Java多线程之生产者消费者问题<一>:使用synchronized keyword解决生产者消费者问题
今天看了一片博文,讲Java多线程之线程的协作,当中作者用程序实例说明了生产者和消费者问题,但我及其它读者发现程序多跑几次还是会出现死锁,百度搜了下大都数的样例也都存在bug,经过细致研究发现当中的问题。并攻克了,感觉有意义贴出来分享下。
以下首先贴出的是有bug的代码,一个4个类。Plate.java:
package CreatorAndConsumer; import java.util.ArrayList;
import java.util.List; /**
* 盘子,表示共享的资源
* @author Martin
*
*/
public class Plate {
private List<Object> eggs = new ArrayList<Object>(); /**
* 获取蛋
* @return
*/
public Object getEgg()
{
System.out.println("消费者取蛋");
Object egg = eggs.get(0);
eggs.remove(0);
return egg;
} /**
* 增加蛋
* @return
*/
public void addEgg(Object egg)
{
System.out.println("生产者生蛋");
eggs.add(egg);
} /**
* 获取蛋个数
* @return
*/
public int getEggNum()
{
return eggs.size();
}
}
消费者类:Consumer2.java
package CreatorAndConsumer; public class Consumer2 implements Runnable {
/**
* 线程资源
*/
private Plate plate; public Consumer2(Plate plate) {
this.plate = plate;
} @Override
public void run() {
synchronized (plate) {
// 假设此时蛋的个数大于0。则等等
while (plate.getEggNum() < 1) {
try {
// 这个细节须要注意,假设线程进入wait,那么其上的锁就会临时得到释放。
// 不然其它线程也不能进行加锁,然后唤醒本线程
plate.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} // 唤醒后。再次得到资源锁。且条件满足就能够放心地取蛋了
plate.getEgg();
plate.notify(); }
}
}
生产者类:Creator2.java
package CreatorAndConsumer; /**
* 生产者
*
* @author Martin
*
*/
public class Creator2 implements Runnable {
/**
* 线程资源
*/
private Plate plate; public Creator2(Plate plate) {
this.plate = plate;
} @Override
public void run() {
synchronized (plate) {
// 假设此时蛋的个数大于0,则等等
while (plate.getEggNum() >= 5) {
try {
// 这个细节须要注意,假设线程进入wait。那么其上的锁就会临时得到释放。
// 不然其它线程也不能进行加锁。然后唤醒本线程
plate.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} // 唤醒后,再次得到资源锁,且条件满足就能够放心地生蛋啦
Object egg = new Object();
plate.addEgg(egg);
plate.notify();
}
}
}
測试类:Tester.java
package CreatorAndConsumer; public class Tester {
public static void main(String[] args)
{
//共享资源
Plate plate = new Plate(); //加入生产者和消费者
for(int i = 0 ; i < 100; i ++)
{
//有bug版
new Thread(new Creator2(plate)).start();
new Thread(new Consumer2(plate)).start(); //无bug版
//new Thread(new Creator(plate)).start();
//new Thread(new Consumer(plate)).start();
}
}
}
假设多执行几次或者将測试类中的循环次数改大,则会发现出现死锁的概率还是非常高的。以下分析发生这样的问题的解决办法:
在jdk中对于Object.wait有这种一段解释:当前线程必须拥有此对象监视器。该线程放弃对此监视器的全部权并等待,直到其它线程通过调用 notify 方法,或 notifyAll 方法通知在此对象的监视器上等待的线程醒来。
可见以下一种情况就可能出现:消费者1进入等待状态(此时资源锁已被放开),假设此时消费者2获取到了资源同步锁(没有人保证消费者1进入等待,下一个拿到锁的一定是生产者)。消费者2推断没有资源也进入等待状态。此时生产者1生产了,并notify了消费者1,消费者1顺利地消费了,并运行notify操作,但此时消费者2却也由于资源而处于等待状态。从而唤醒了消费者2(消费者1本欲唤醒其它生产者)。而此时并没有不论什么资源。导致了整个程序由于消费者2陷入无限的等待。形成了死锁。
经过以上分析。究其根本原因是:同一时候几个消费者或几个生产者处于等待状态,导致消费者可能唤醒的还是消费者,或者生产者唤醒的还是生产者。那么假设我们可以保证同一时候仅仅有一个消费者处于wait状态(生产者同理),那就就能保证消费者唤醒的一定是生产者,从而能使整个任务顺利进行下去。以下是改动后的代码:
改进后的Consumer.java
package CreatorAndConsumer; public class Consumer implements Runnable {
/**
* 线程资源
*/
private Plate plate; /**
* 生产者锁:用于锁定同一时间仅仅能有一个生产者进入生产临界区(假设同一时候又两个生产者进入临界区。那么非常有可能当中一个生产者本想唤醒消费者却唤醒了生产者)
*/
private static Object consumerLocker = new Object(); public Consumer(Plate plate) {
this.plate = plate;
} @Override
public void run() {
// 必须先获得生产者锁才干生产
synchronized (consumerLocker) {
synchronized (plate) {
// 假设此时蛋的个数大于0,则等等
while (plate.getEggNum() < 1) {
try {
// 这个细节须要注意,假设线程进入wait,那么其上的锁就会临时得到释放。
// 不然其它线程也不能进行加锁,然后唤醒本线程
plate.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} // 唤醒后,再次得到资源锁,且条件满足就能够放心地取蛋了
plate.getEgg();
plate.notify(); }
}
}
}
改进后的Creator.java:
package CreatorAndConsumer; /**
* 生产者
*
* @author Martin
*
*/
public class Creator implements Runnable {
/**
* 线程资源
*/
private Plate plate; /**
* 生产者锁:用于锁定同一时间仅仅能有一个生产者进入生产临界区(假设同一时候又两个生产者进入临界区。那么非常有可能当中一个生产者本想唤醒消费者却唤醒了生产者)
*/
private static Object creatorLocker = new Object(); public Creator(Plate plate) {
this.plate = plate;
} @Override
public void run() {
//必须先获得生产者锁才干生产
synchronized (creatorLocker) {
synchronized (plate) {
// 假设此时蛋的个数大于0,则等等
while (plate.getEggNum() >= 5) {
try {
// 这个细节须要注意。假设线程进入wait,那么其上的锁就会临时得到释放。
// 不然其它线程也不能进行加锁,然后唤醒本线程
plate.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} // 唤醒后,再次得到资源锁,且条件满足就能够放心地生蛋啦
Object egg = new Object();
plate.addEgg(egg);
plate.notify();
}
}
}
}
改进说明:改进后的生产者和消费者分别加了生产者锁和消费者锁。分别用于锁定同一时间仅仅能有一个消费者(生产者)进入生产临界区(假设同一时候又两个生产者进入临界区,那么非常有可能当中一个生产者本想唤醒消费者却唤醒了生产者),总的来说就是一共三个锁:消费者锁、生产者锁、生产者和消费者共享的锁。
终于,写多线程的时候须要注意的是一个资源可能唤醒的是全部因该资源而等待的线程,因此消费者线程不一定唤醒的就是生产者线程也可能是消费者线程。
Java多线程之生产者消费者问题<一>:使用synchronized keyword解决生产者消费者问题的更多相关文章
- 浅谈Java多线程同步机制之同步块(方法)——synchronized
在多线程访问的时候,同一时刻只能有一个线程能够用 synchronized 修饰的方法或者代码块,解决了资源共享.下面代码示意三个窗口购5张火车票: package com.jikexueyuan.t ...
- Java多线程同步机制之同步块(方法)——synchronized
在多线程访问的时候,同一时刻只能有一个线程能够用 synchronized 修饰的方法或者代码块,解决了资源共享.下面代码示意三个窗口购5张火车票: package com.jikexueyuan.t ...
- java多线程中篇(二) —— 线程的创建和Synchronized锁关键字
学习之前,先了解线程状态图 说明:线程共包括以下5种状态. 1. 新建状态(New) : 线程对象被创建后,就进入了新建状态.例如,Thread thread = new Thread ...
- Java多线程基础知识(二)
一. Java线程具有6种状态 NEW 初始状态,线程被创建,但是还没有调用start方法. RUNNABLE 运行状态,java线程将操作系统中的就绪和运行两种状态笼统的称作进行中. BLOCKE ...
- [Java] 多线程基础详细总结,附加详细实例
详细代码在文章底部 目录 基础概念 进程与线程 单线程与多线程 实现线程的4中方式 thread.start()和runnable.run()的区别 Thread和Runnable的异同 线程的基本操 ...
- Java多线程-两种常用的线程计数器CountDownLatch和循环屏障CyclicBarrier
Java多线程编程-(1)-线程安全和锁Synchronized概念 Java多线程编程-(2)-可重入锁以及Synchronized的其他基本特性 Java多线程编程-(3)-从一个错误的双重校验锁 ...
- synchronized与static synchronized 的差别、synchronized在JVM底层的实现原理及Java多线程锁理解
本Blog分为例如以下部分: 第一部分:synchronized与static synchronized 的差别 第二部分:JVM底层又是怎样实现synchronized的 第三部分:Java多线程锁 ...
- Java多线程并发编程/锁的理解
一.前言 最近项目遇到多线程并发的情景(并发抢单&恢复库存并行),代码在正常情况下运行没有什么问题,在高并发压测下会出现:库存超发/总库存与sku库存对不上等各种问题. 在运用了 限流/加锁等 ...
- Java 多线程与并发【原理第一部分笔记】
Java 多线程与并发[原理第一部分笔记] Synchronized synchronized的基本含义以及使用方式 在Java中线程安全问题的主要诱因就是存在共享数据(也称为临界资源)以及存在多条线 ...
随机推荐
- Windows安装绿色版git管理软件GitStack 2.3.8
1.原来 GitStack 是安装在局域网的,为了更好开展工作,迁移到公网的服务器.(安全性未知) 2.公网服务器已经在运行一个 Apache 2.4 (占用80端口): 3.GitStack 2. ...
- Codeforces Round #354 (Div. 2) E. The Last Fight Between Human and AI 数学
E. The Last Fight Between Human and AI 题目连接: http://codeforces.com/contest/676/problem/E Description ...
- webpack4 + vue + vue-router + vuex
ps: 所有案例使用的 node 及 npm 版本如下 node版本: v8.4.0 npm: 5.3.0 下一个案例默认是接着上一个继续写的 建议先熟悉以下文档 vue vue-router vue ...
- 该死的Ubuntu 16.04不自动续租DHCP的IP
BUG,这是一个BUG,参考:https://bugs.launchpad.net/ubuntu/+source/isc-dhcp/+bug/1551351,如果不自动续租IP,导致的问题就是网线灯还 ...
- composer安装Workerman报错:Installation failed, reverting ./composer.json to its original content.
今天想在TP5上安装workerman,实现一个后台消息提醒功能. 第一步就卡住了,根据手册里说的首先通过composer安装 $ composer require topthink/think-wo ...
- C 常量的类型
http://bbs.csdn.net/topics/380028485 整型常量的类型是下列相应表中第一个能表示其值的类型: int --> long int --> long long ...
- ASP.NET web.config中<customErrors>节点说明
customErrors>节点用于定义一些自定义错误信息的信息.此节点有Mode和defaultRedirect两个属性,其中defaultRedirect属性是一个可选属性,表示应用程序发生错 ...
- kafka分区原理图
一个Topic的多个分区,被分布在kafka集群中的多个server上.每一个分区都有一个server为"leader";leader负责全部的读写操作,假设leader失效,那么 ...
- Linux架构和目录-基础篇
1.Linux目录结构 2. /boot/ 存放系统内核文件,如vmlinuz,initrd,System.map等.其中, a. vmlinuz是可引导的.压缩的内核,“vm”即“Virtual M ...
- python接口自动化27-urlencode编码%E6%82%A0%E6%82%A0与解码
前言 urllib.parse 里面三个方法:urlencode,quote,unquote详解. 在做接口自动化过程中,http协议在发送url的时候,是以urlencode的编码格式传过去的,通常 ...