处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象。这时候, 我们就需要用到“线程同步” 。
线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕后,下一个线程再使用。
由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突的问题。为了保证数据在方法中被访问时的正确性,在访问时加入锁机制(synchronized)。
当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可。存在以下问题:
由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需针对方法提出一套机制,这套机制就是synchronized关键字,它包括两种用法: synchronized 方法和synchronized 块。
public synchronized void method(int args) {}
synchronized方法控制对“成员变量|类变量”对象的访问:
每个对象对应一把锁, 每个synchronized方法都必须获得调用该方法的对象的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。
缺陷:若将一个大的方法声明为synchronized将会大大影响效率。
同步块: synchronized (obi) {}, obi称之为同步监视器。
obj可以是任何对 象,但是推荐使用共享资源作为同步监视器。
同步方法中无需指定同步监视器,因为同步方法的同步监视器是this即该对象本身,或class即类的模子。
同步监视器的执行过程:
1、可能有重复数据:每个线程的工作空间与主内存在进行数据交互时,不一致(来不及更新数据,就完成了拷贝工作)。
2、不应该出现的负数:在模拟12306抢票中,对于最后一张票,即数据到临界值时,sleep等待完的线程拿不到“余票”。
我们之前有模拟过12306抢票,我们再来看一下:
public class UnsafeTest1 {
public static void main(String[] args) {
//一份资源
Web12306 web = new Web12306();
//多个代理
new Thread(web,"小明").start();
new Thread(web,"小军").start();
new Thread(web,"小强").start();
}
}
class Web12306 implements Runnable{
//票数
private int ticketNums = 20;
private boolean flag = true;
public void run(){
while(flag){
test();
}
}
public void test(){
if(ticketNums<0){
flag=false;
return;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);
}
}
对于数据又改又读取的情况,需要并发控制
public class UnsafeTest2 {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
for(int i=0;i<10000;i++){
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
System.out.println(list.size());
}
}
在并发是保证数据的准确性,效率尽可能高。
public class SynTest1 {
public static void main(String[] args) {
//一份资源
SafeWeb12306 web = new SafeWeb12306();
//多个代理
new Thread(web,"小明").start();
new Thread(web,"小军").start();
new Thread(web,"小强").start();
}
}
class SafeWeb12306 implements Runnable{
//票数
private int ticketNums = 20;
private boolean flag = true;
public void run(){
while(flag){
test();
}
}
//线程安全,同步
//给资源上锁,即锁的是使用此方法的对象,让其他方法无法再访问,只能排队等待
public synchronized void test(){
if(ticketNums<=0){
flag=false;
return;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);
}
}
给资源上锁,即锁的是使用此方法的对象,让其他方法无法再访问,只能排队等待。
public class SynTest3 {
public static void main(String[] args) {
//一份资源
SynWeb12306 web = new SynWeb12306();
//多个代理
new Thread(web,"小明").start();
new Thread(web,"小军").start();
new Thread(web,"小强").start();
}
}
class SynWeb12306 implements Runnable{
//票数
private int ticketNums = 20;
private boolean flag = true;
public void run(){
while(flag){
//test1();
test2();
}
}
//double checking双重检测
public void test2(){
if(ticketNums<=0){ //考虑的是没有票的情况
flag=false;
return;
}
synchronized(this){
if(ticketNums<=0){ //考虑的是最后一张票的情况
flag=false;
return;
}
//模拟网络延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);
}
}
//线程安全:同步方法
public synchronized void test1(){
if(ticketNums<=0){
flag=false;
return;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);
}
}
小红持有口红,1S后想要拿到镜子,而此时小强持有镜子,2S后想要拿到口红。两人彼此等待对方放下手中的镜子或口红,从而陷入僵局。
public class DeadLock {
public static void main(String[] args) {
Markup g1 = new Markup(1,"小红");
Markup g2 = new Markup(0,"小强");
g1.start();
g2.start();
}
}
//口红
class Lipstick{
}
//镜子
class Mirror{
}
//化妆
class Markup extends Thread{
static Lipstick lipstick=new Lipstick();
static Mirror mirror=new Mirror();
//选择
int choice;
//名字
String girl;
public Markup(int choice,String girl){
this.choice=choice;
this.girl = girl;
}
public void run(){
markup();
}
private void markup(){
if(choice==0){
synchronized(lipstick){
System.out.print(this.girl+"涂口红\r\n");
//1s后想拥有镜子的锁
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(mirror){
System.out.print(this.girl+"照镜子\r\n");
}
}
}else{
synchronized(mirror){
System.out.print(this.girl+"照镜子");
//2s后想拥有口红的锁
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(lipstick){
System.out.print(this.girl+"涂口红");
}
}
}
}
}
解决方法:不要在同一个代码块中,同时持有多个对象的锁。
不要锁套锁
synchronized(mirror){
System.out.print(this.girl+"照镜子");
//2s后想拥有口红的锁
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//把锁分开
synchronized(lipstick){
System.out.print(this.girl+"涂口红");
}
这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件:
对于生产者,没有生产产品之前,要通知消费者等待。而生产了产品之后,又需要 马上通知消费者消费。
对于消费者,在消费之后,要通知生产者已经消费结束,需要继续生产新产品以供消费。
红绿灯法又叫标识法,通过设定标志位,提醒对象释放锁。
方法名 | 作用 |
---|---|
final void wait() | 标识线程一直等待,直到其他线程通知,与sleep不同,会释放锁 |
final void wait(long timeout) | 指定等待的毫秒数 |
final void notifiy() | 唤醒一个处于等待状态的线程 |
final void notifiyAll() | 唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度 |