赞
踩
一个公共类存储互斥资源(3方法,分别打印1,2,3),基于同一个该类实例,创建三个并发线程t1,t2,t3,t1调用实例的printFirst()方法打印1,t2调用实例的printSecond()方法打印2,t3调用实例的printThird()方法打印3,如何保证三个并发线程顺序执行成功打印出1,2,3呢?
这个问题思路很简单,在JUC当中属于入门级别了,面试中常问的“3个并发线程顺序打印1-100”也和这个差不多。
创建变量flag作为互斥变量(标记线程号),当flag=1,则表示当前方法只轮到线程1执行,flag=2,则表示当前方法只轮到线程2执行,flag=3,则表示当前方法只轮到线程3执行,以此保证打印方法的顺序性。例如当线程3提前执行了,发现当前公共类中的flag=1(即线程1还没执行打印),那么就说明来早了,得候着。
于是乎直接在公共类中创建一把锁,并使用synchronized 关键字,来做到这个效果,如下所示
public class ShareClass { private int flag = 1; //标记下一个应该执行的线程号 private final Object obj = new Object();//创建一把锁 public void printFirst() throws InterruptedException { synchronized(obj){ while(this.flag != 1){ obj.wait();//来早了就等着,阻塞在此 } System.out.println("1"); this.flag = 2;//1已经打印好了,修改下一个应该执行的线程号 obj.notifyAll();//唤醒其他阻塞线程 } } public void printSecond() throws InterruptedException { synchronized(obj){ while(this.flag != 2){ obj.wait();//来早了就等着,阻塞在此 } System.out.println("2"); this.flag = 3;//2已经打印好了,修改下一个应该执行的线程号 obj.notifyAll();//唤醒其他阻塞线程 } } public void printThird() throws InterruptedException { synchronized(obj){ while(this.flag != 3){ obj.wait();//来早了就等着,阻塞在此 } System.out.println("3"); obj.notifyAll();//唤醒其他阻塞线程 } } }
创建main方法测试试试
public class TestDemo{ public static void main(String[] args) throws InterruptedException { final ShareClass s= new ShareClass(); Thread t1 = new Thread(){ @Override public void run() { try { s.printFirst(); } catch (InterruptedException e) { e.printStackTrace(); } } }; Thread t2 = new Thread(){ @Override public void run() { try { s.printSecond(); } catch (InterruptedException e) { e.printStackTrace(); } } }; Thread t3 = new Thread(){ @Override public void run() { try { s.printThird(); } catch (InterruptedException e) { e.printStackTrace(); } } }; t3.start(); t2.start(); t1.start(); } }
测试结果完全没问题。
为什么下面的代码中,要用 while 而不是 if ? if不行吗?
synchronized(obj){
while(this.flag != 1){
obj.wait();//来早了就等着,阻塞在此
}
this.flag = 2;//1已经打印好了,修改下一个应该执行的线程号
obj.notifyAll();//唤醒其他阻塞线程
}
这就要理解一下obj.wait();
这句代码的含义了,这句代码的意思是阻塞当前线程,释放锁,等待被唤醒再重新执行,而被唤醒的线程需要重新竞争锁对象,重新获得锁后则是从wait处继续往下执行。
那么假如把 while 换成 if 的话,我们来考虑以下情况:
假设线程3先拿到锁,此时发现flag=1,来早了,开始阻塞,释放锁。
接着线程1抢到了锁,发现此时flag=1,正常打印1,并且将flag修改为2,并唤醒其他阻塞线程,线程执行完毕,关键点来了。
此时线程3被唤醒,和线程2重新竞争锁,假设此时线程3再次抢到了锁,它会直接往下执行代码,“从wait处继续往下执行”,此时flag=2,原本应由线程2来打印,但是此时跳过判断了,直接打印出了3,接着再次释放锁,线程执行完毕。
此时当锁落到线程2手中时,3已经被打印了,于是打印出了1,3,2的情况出来了。
所以判断flag处使用 while 而不是 if 这个细节,你get到了吗?可以体会一下…
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。