线程间通信的模型有两种:共享内存和消息传递,以下方式都是基本这两种模型来实现的。我们来基本一道面试常见的题目来分析 :
场景---两个线程,一个线程对当前数值加 1,另一个线程对当前数值减 1,要求用线程间通信
一、synchronized实现
/** * 实现线程A对一个值+1,线程B对该值-1 */ //第一步:创建资源类,定义属性和操作方法 class Share{ //目标值 int number = 0; //+1操作 public synchronized void incr() throws InterruptedException { //第二步:判断->操作->通知 //判断 if (number != 0){ this.wait(); } //操作 number++; System.out.println(Thread.currentThread().getName()+":"+number); //通知其他线程 this.notifyAll(); } //-1操作 public synchronized void decr() throws InterruptedException { if (number != 1){ this.wait(); } number--; System.out.println(Thread.currentThread().getName()+":"+number); this.notifyAll(); } } public class ThreadDemo01 { //第二步,创建多个线程,调用资源类中的操作方法 public static void main(String[] args) { Share share = new Share(); new Thread(()->{ for (int i = 0; i < 10; i++) { try { share.incr(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "线程A").start(); new Thread(()->{ for (int i = 0; i < 10; i++) { try { share.decr(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "线程B").start(); } }
二、虚假唤醒问题
现在我们将进程变为四个,A、C进程负责加,B、D进程负责减
//第一步:创建资源类,定义属性和操作方法 class Share{ //目标值 int number = 0; //+1操作 public synchronized void incr() throws InterruptedException { //第二步:判断->操作->通知 //判断 if (number != 0){ this.wait(); } //操作 number++; System.out.println(Thread.currentThread().getName()+":"+number); //通知其他线程 this.notifyAll(); } //-1操作 public synchronized void decr() throws InterruptedException { if (number != 1){ this.wait(); } number--; System.out.println(Thread.currentThread().getName()+":"+number); this.notifyAll(); } } public class ThreadDemo01 { //第二步,创建多个线程,调用资源类中的操作方法 public static void main(String[] args) { Share share = new Share(); new Thread(()->{ for (int i = 0; i < 10; i++) { try { share.incr(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "线程A").start(); new Thread(()->{ for (int i = 0; i < 10; i++) { try { share.decr(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "线程B").start(); new Thread(()->{ for (int i = 0; i < 10; i++) { try { share.incr(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "线程C").start(); new Thread(()->{ for (int i = 0; i < 10; i++) { try { share.decr(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "线程D").start(); } }
我们可以看到,结果并不是严格的一加一减,而是出现了2、3这样的数字,这是为什么呢?
这就是要讲的虚假唤醒的问题。
查阅jdk1.8版本的API,可以看到wait方法中有这样一段话:
意思是,我们代码中的wait应该放在循环中来避免虚假唤醒,不应该写成:
1. if (number != 0){ 2. this.wait(); 3. }
而应该写成:
1. while (number != 0){ 2. this.wait(); 3. }
为什么会导致这样呢?什么是虚假唤醒呢?
简而言之,就是wait()方法在唤醒之后,会直接在之前等待的地方继续执行,而不会再执行前面的判断了,这就叫做虚假唤醒。所以要放在循环中,让他唤醒后重新去做判断,避免虚假唤醒的问题。
所以Share类中正确的代码应是:
class Share{ //目标值 int number = 0; //+1操作 public synchronized void incr() throws InterruptedException { //第二步:判断->操作->通知 //判断 while (number != 0){ this.wait(); } //操作 number++; System.out.println(Thread.currentThread().getName()+":"+number); //通知其他线程 this.notifyAll(); } //-1操作 public synchronized void decr() throws InterruptedException { while (number != 1){ this.wait(); } number--; System.out.println(Thread.currentThread().getName()+":"+number); this.notifyAll(); } }
运行结果:
线程A:1 线程B:0 线程A:1 线程B:0 线程C:1 线程D:0 线程A:1 线程D:0 线程C:1 线程B:0 线程C:1 线程D:0 线程A:1 线程D:0 线程C:1 线程B:0 线程C:1 线程D:0 线程A:1 线程D:0 线程C:1 线程B:0 线程C:1 线程D:0 线程A:1 线程D:0 线程C:1 线程B:0 线程C:1 线程D:0 线程A:1 线程D:0 线程C:1 线程B:0 线程A:1 线程B:0 线程A:1 线程B:0 线程A:1 线程B:0 Process finished with exit code 0
三、Lock实现四线程操作
/** * Lock实现:线程A、C将number值从0变为1,线程B、D将number值从1变为0 */ class Share{ private int number = 0; private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); //+1操作 public void incr() throws InterruptedException { lock.lock(); try{ while(number != 0){ condition.await(); } number++; System.out.println(Thread.currentThread().getName()+":"+number); condition.signalAll(); }finally { lock.unlock(); } } //-1操作 public void decr() throws InterruptedException { lock.lock(); try { while(number != 1){ condition.await(); } number--; System.out.println(Thread.currentThread().getName()+":"+number); condition.signalAll(); } finally { lock.unlock(); } } } public class ThreadDemo02 { public static void main(String[] args) { Share share = new Share(); new Thread(()->{ for (int i = 0; i < 10; i++) { try { share.incr(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "线程A").start(); new Thread(()->{ for (int i = 0; i < 10; i++) { try { share.decr(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "线程B").start(); new Thread(()->{ for (int i = 0; i < 10; i++) { try { share.incr(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "线程C").start(); new Thread(()->{ for (int i = 0; i < 10; i++) { try { share.decr(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "线程D").start(); } }
运行结果:
线程A:1 线程D:0 线程A:1 线程B:0 线程C:1 线程D:0 线程A:1 线程B:0 线程C:1 线程D:0 线程A:1 线程B:0 线程C:1 线程D:0 线程A:1 线程B:0 线程C:1 线程D:0 线程A:1 线程B:0 线程C:1 线程D:0 线程A:1 线程B:0 线程C:1 线程D:0 线程A:1 线程B:0 线程C:1 线程D:0 线程A:1 线程B:0 线程C:1 线程D:0 线程A:1 线程B:0 线程C:1 线程D:0 线程C:1 线程B:0