java线程基础巩固---多线程下的生产者消费者模型,以及详细介绍notifyAll方法
在上一次【http://www.cnblogs.com/webor2006/p/8419565.html】中演示了多Product多Consumer假死的情况,这次解决假死的情况来实现一个真正的多线程下的生产者消费者模型,在解决之前来看一下wait()和notify()的官方文档,因为假死的原因就跟这两方法有关:
而其中0就代表永远等待,如果给wait中传一个大于0的参数那就是wait指定时间之后就不wait了,好继续往下看wait()的官方注释:
其这句话说到了一个重点:调用wait()方法其实是释放了监听器的所有权,并且当被唤醒之后并非立马就能够执行,而是需要再去获取直接获取成功之后才会执行它下面的代码。而对于wait()过的线程是需要能过notify()或notifyAll()来唤醒的,而notify()是通知一个线程唤醒,而notifyAll()是会将wait()在同一个monitor的所有线程都会唤醒,而解决之前多个生产者与消费者死锁就得用到notifyAll()这个方法了,下面来用它将死锁的程序进行改造,再改之前先贴一下原来有BUG的代码:
public class ProductConsumerVersion2 { private final Object LOCK = new Object(); private int i = 1; /* 此标识用来说明是否当前已经生产过了,默认没有 */ private volatile boolean isProduced = false; private void product() { synchronized (LOCK) { if (isProduced) { try { System.out.println(Thread.currentThread().getName() + " wait了"); LOCK.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } else { i++; System.out.println(Thread.currentThread().getName() + " 生产了-->" + i); LOCK.notify(); System.out.println(Thread.currentThread().getName() + " notify了"); isProduced = true; } } } private void comsume() { synchronized (LOCK) { if (isProduced) { System.out.println(Thread.currentThread().getName() + " 消费了-->" + i); LOCK.notify(); System.out.println(Thread.currentThread().getName() + " notify了"); isProduced = false; } else { try { System.out.println(Thread.currentThread().getName() + " wait了"); LOCK.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static void main(String[] args) { ProductConsumerVersion2 productConsumerVersion2 = new ProductConsumerVersion2(); Stream.of("P1", "P2").forEach(n -> new Thread(n) { @Override public void run() { while (true) productConsumerVersion2.product(); } }.start()); Stream.of("C1", "C2").forEach(n -> new Thread(n) { @Override public void run() { while (true) productConsumerVersion2.comsume(); } }.start()); } }
好,接下来进行改造:
类似的,对于消费方法也进行相应的修改:
那为什么要用while进行改造呢?如果改用if就不行么,那假设改用if也可以,那咱们来分析下:
如果有两个生产者线程p1、p2,一个消费者线程c1,目前队列中已经有一个数据待c1进行消费,所以此时p1、p2都已经wait()住了,因为得待c1消费完来notifyall();这时c1将队列中的数据消费掉了然后用notifyAll()通知p1、p2进行数据生产,此时p1抢到锁了,于是乎会往下执行数据生产,如下:
而当执行完p1就释放锁了,此时正在wait()的p2抢到锁之后由于是if,所以也开始生产数据了,这样就出现一个尴尬的局面:生产了两个数据,然后才开始消费,而咱们预期的是生产一个消费一个,所以这就是为啥需要用while的原因所在,下面用程序来演示一下这种异常情况:
public class ProductConsumerVersion3 { private final Object LOCK = new Object(); private int i = 1; /* 此标识用来说明是否当前已经生产过了,默认没有 */ private volatile boolean isProduced = false; private void product() { synchronized (LOCK) { if (isProduced) { try { // System.out.println(Thread.currentThread().getName() + " wait了"); LOCK.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } i++; System.out.println(Thread.currentThread().getName() + " 生产了-->" + i); LOCK.notifyAll(); // System.out.println(Thread.currentThread().getName() + " notify了"); isProduced = true; } } private void comsume() { synchronized (LOCK) { if (!isProduced) { try { // System.out.println(Thread.currentThread().getName() + " wait了"); LOCK.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName() + " 消费了-->" + i); LOCK.notifyAll(); // System.out.println(Thread.currentThread().getName() + " notify了"); isProduced = false; } } public static void main(String[] args) { ProductConsumerVersion3 productConsumerVersion2 = new ProductConsumerVersion3();
//用三个生产者和二个消费者来模式,貌似生产者与消费者的个数一样难得出我们预期的异常情况 Stream.of("P1", "P2", "P3").forEach(n -> new Thread(n) { @Override public void run() { while (true) { productConsumerVersion2.product(); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } }.start()); Stream.of("C1", "C2").forEach(n -> new Thread(n) { @Override public void run() { while (true) { productConsumerVersion2.comsume(); try {//为了便于观察打印这里小休眠一会 Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } }.start()); } }
编译运行:
解决办法也就是将其if改为while啦,如下:
public class ProductConsumerVersion3 { private final Object LOCK = new Object(); private int i = 1; /* 此标识用来说明是否当前已经生产过了,默认没有 */ private volatile boolean isProduced = false; private void product() { synchronized (LOCK) { while (isProduced) { try { // System.out.println(Thread.currentThread().getName() + " wait了"); LOCK.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } i++; System.out.println(Thread.currentThread().getName() + " 生产了-->" + i); LOCK.notifyAll(); // System.out.println(Thread.currentThread().getName() + " notify了"); isProduced = true; } } private void comsume() { synchronized (LOCK) { while (!isProduced) { try { // System.out.println(Thread.currentThread().getName() + " wait了"); LOCK.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName() + " 消费了-->" + i); LOCK.notifyAll(); // System.out.println(Thread.currentThread().getName() + " notify了"); isProduced = false; } } public static void main(String[] args) { ProductConsumerVersion3 productConsumerVersion2 = new ProductConsumerVersion3(); Stream.of("P1", "P2", "P3").forEach(n -> new Thread(n) { @Override public void run() { while (true) { productConsumerVersion2.product(); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } }.start()); Stream.of("C1", "C2").forEach(n -> new Thread(n) { @Override public void run() { while (true) { productConsumerVersion2.comsume(); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } }.start()); } }
编译运行:
所以下面对其总结一下:
1、为啥木有死锁了,是因为将notify改为notifyAll了。
2、为啥生产消费木有错乱,是因为使用了while循环来判断是不需要进行wait()。