在上一次【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()。

最新文章

  1. [Bind(Exclude = "OrderId")][ScaffoldColumn(false)]
  2. 我的Objective-C系列文章
  3. Python语法一
  4. Mono 之 Jexus
  5. Java实战之04JavaWeb-07Listener和Filter
  6. 一键安装 gitlab7 on rhel6.4 并设置邮件发送
  7. addEventListener与removeEventListener
  8. 阻塞IO
  9. Guava 教程2-深入探索 Google Guava 库
  10. eclipse对于标签的配置不会出现自动提示的解决
  11. Spring4 MVC Hibernate4 maven集成
  12. Linux课题实践四——ELF文件格式分析
  13. video 播放本地视屏
  14. Elasticsearch利用scroll查询获取所有数据
  15. iOS获取时间、日期
  16. 华为5573+联通4G上网SIM+ROS hap ac-RB962UiGS-5HacT2HnT 上网
  17. 四则运算生成器(java) 蔡苑菲,陆海燕
  18. 51Nod 1554 欧姆诺姆和项链 (KMP)
  19. [JAVA]Apache FTPClient操作“卡死”问题的分析和解决
  20. how to identify your .NET Framework version

热门文章

  1. 【AMAD】django-oauth2-provider -- 为你的app提供Oauth2的访问
  2. eclipse 如何从Gitee.com克隆工程到本地,并运行
  3. [bzoj2288]【POJ Challenge】生日礼物_贪心_堆
  4. python3.6下pycharm连接mysql
  5. matplotlib库之直方图
  6. selenium的使用与chromedriver的下载配置
  7. sqlalchemy定义mysql时间戳字段
  8. 搞懂Dubbo SPI可拓展机制
  9. react中数据持久化缓存redux-persist
  10. squoosh