生产者和消费者案例是多线程通讯中一个经典案例。如今Buffer里边有初始值为0的产品,要求生产者没生产一个产品,消费者就消费一个产品。这是一个简单的案例,使用多线程的等待唤醒机制就能轻松解决。java
生产者消费者编码实现多线程
class Buffer{ private int num = 0; boolean flag = true; public void increase(){ synchronized (this){ if(!flag){ try { this.wait(); } catch (Exception e) { e.printStackTrace(); } } num++; flag = false; System.out.println(Thread.currentThread().getName()+" : "+num); this.notifyAll(); } } public void decrease(){ synchronized (this){ if(flag){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } num--; flag = true; System.out.println(Thread.currentThread().getName()+" : "+num); this.notifyAll(); } } } public class ThreadCommunication { public static void main(String[] args) { Buffer buffer = new Buffer(); new Thread(()->{ for (int i = 0; i < 10; i++) { buffer.increase(); } },"AAA").start(); new Thread(()->{ for (int i = 0; i < 10; i++) { buffer.decrease(); } },"BBB").start(); } } AAA : 1 BBB : 0 AAA : 1 BBB : 0 AAA : 1 BBB : 0 AAA : 1 BBB : 0 AAA : 1 BBB : 0
以上代码在只有一个生产者一个消费者的两个线程中没有什么问题,可是生产者消费者一旦多起来就可能产生问题。如今要求两个生产者生产,两个消费者消费。这样就可能产生虚假唤醒的问题。this
class Buffer{ private int num; boolean flag = true; public void increase(){ synchronized (this){ if(!flag){ try { this.wait(); } catch (Exception e) { e.printStackTrace(); } } num++; flag = false; System.out.println(Thread.currentThread().getName()+" : "+num); this.notifyAll(); } } public void decrease(){ synchronized (this){ if(flag){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } num--; flag = true; System.out.println(Thread.currentThread().getName()+" : "+num); this.notifyAll(); } } } public class ThreadCommunication { public static void main(String[] args) { Buffer buffer = new Buffer(); new Thread(()->{ for (int i = 0; i < 5; i++) { buffer.increase(); } },"AAA").start(); new Thread(()->{ for (int i = 0; i < 5; i++) { buffer.decrease(); } },"BBB").start(); new Thread(()->{ for (int i = 0; i < 5; i++) { buffer.increase(); } },"CCC").start(); new Thread(()->{ for (int i = 0; i < 5; i++) { buffer.decrease(); } },"DDD").start(); } } AAA : 1 BBB : 0 AAA : 1 BBB : 0 AAA : 1 BBB : 0 AAA : 1 BBB : 0 CCC : 1 AAA : 2 CCC : 3 DDD : 2 BBB : 1 DDD : 0 CCC : 1 DDD : 0 CCC : 1 DDD : 0 CCC : 1 DDD : 0
出现问题CCC : 1 AAA : 2 CCC : 3 这就是虚假唤醒问题编码
虚假唤醒产生的缘由:在上面的Demo中有AC两个生产者和BD两个消费者。不管是生产者仍是消费者每次生产或者消费一个产品以后都要唤醒全部的阻塞线程再次竞争共享资源Buffer。若是wait前面的判断条件用的是if就可能存在下面一种情景:生产者已经生产一件产品(num = 1),flag=flase。此时就会唤醒全部的ABCD四个线程,线程
生产者A得到Buffer的锁对象,进入判断发现flag==false就会阻塞。code
生产者C得到Buffer的锁对象,进入判断发现flag==false对象也会阻塞。对象
消费者B得到Buffer的锁对象,进入判断flag=flase,正常执行完毕唤醒全部的阻塞线程,将flag设置tureblog
生产者A得到Buffer的锁对象,因为阻塞的时候已经用if判断过flag,线程被唤醒的时候直接从阻塞的地方开始执行,唤醒全部阻塞线程,将flag设置flase资源
生产者A得到Buffer的锁对象,因为阻塞的时候已经用if判断过flag,唤醒时候从阻塞地方开始执行。虽然flag为false,可是不会从新判断不会致使当前线程阻塞。致使num连续自增两次。get
从上面能够看出形成虚假唤醒的缘由就是阻塞线程被唤醒的时候要从中断地方开始执行,若是用if作wait的判断条件会致使唤醒以后不加判断直接执行致使程序出错。所以解决方案就是使用while替代if做为判断条件。即便是阻塞以后唤醒也要从新判断。官方的API也对这个问题有说明
总结:多线程使用等待唤醒机制要防止虚假唤醒问题,wait前面的判断条件不能使用if要使用while
class Buffer{ private int num; boolean flag = true; public void increase(){ synchronized (this){ //wait的判断条件为了防止虚假唤醒要使用while作判断 while(!flag){ try { this.wait(); } catch (Exception e) { e.printStackTrace(); } } num++; flag = false; System.out.println(Thread.currentThread().getName()+" : "+num); this.notifyAll(); } } public void decrease(){ synchronized (this){ //为了防止虚假唤醒要使用while作判断 if(flag){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } num--; flag = true; System.out.println(Thread.currentThread().getName()+" : "+num); this.notifyAll(); } } } public class ThreadCommunication { public static void main(String[] args) { Buffer buffer = new Buffer(); new Thread(()->{ for (int i = 0; i < 5; i++) { buffer.increase(); } },"AAA").start(); new Thread(()->{ for (int i = 0; i < 5; i++) { buffer.decrease(); } },"BBB").start(); new Thread(()->{ for (int i = 0; i < 5; i++) { buffer.increase(); } },"CCC").start(); new Thread(()->{ for (int i = 0; i < 5; i++) { buffer.decrease(); } },"DDD").start(); } } AAA : 1 BBB : 0 AAA : 1 BBB : 0 AAA : 1 BBB : 0 AAA : 1 BBB : 0 AAA : 1 BBB : 0 CCC : 1 DDD : 0 CCC : 1 DDD : 0 CCC : 1 DDD : 0 CCC : 1 DDD : 0 CCC : 1 DDD : 0