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中线程安全问题的主要诱因就是存在共享数据(也称为临界资源)以及存在多条线 ...
随机推荐
- java多线程技术之(callable和future)
接着上一篇继续并发包的学习,本篇说明的是Callable和Future,它俩很有意思的,一个产生结果,一个拿到结果. Callable接口类似于Runnable,从名字就可以看出来了,但是Runnab ...
- 90. 子集 II
90. 子集 II 题意 给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集). 说明:解集不能包含重复的子集. 示例: 输入: [1,2,2]输出:[ [2], [1], ...
- Minimum Size Subarray Sum 最短子数组之和
题意 Given an array of n positive integers and a positive integer s, find the minimal length of a suba ...
- ASP.NET 构建高性能网站 第1篇
网站优化需要考虑的方面 在用ASP.NET开发网站的时候,性能是永远需要考虑和关注的问题,性能不仅仅只是程序代码执行时候的速度,而是涉及到方方面面的东西. 就拿ASP.NET的一个请求来讲,从浏览器向 ...
- Git_忽略特殊文件
有些时候,你必须把某些文件放到Git工作目录中,但又不能提交它们,比如保存了数据库密码的配置文件啦,等等,每次git status都会显示“Untracked files ...”,有强迫症的童鞋心里 ...
- 模仿JQuery 的添加多个事件的原理
var jquery=function(dom){ var obj={ ready:function(fn){ if(typeof dom.onload=="function"){ ...
- Installing Node.js via package manager | Node.js
Installing Node.js via package manager | Node.js i386 (32-bit)
- Running CMD.EXE as Local System(转)
Many times in the past I had to run an interactive command-line shell under the Local SYSTEM account ...
- Window 8 启用 Telnet 命令工具一览图
Window 8 启用 Telnet 命令工具一览图 太阳火神的漂亮人生 (http://blog.csdn.net/opengl_es) 本文遵循"署名-非商业用途-保持一致"创 ...
- [Phonegap+Sencha Touch] 移动开发18 Sencha Touch项目通过phonegap打包后的程序名字的问题
之前说过 sencha phonegap init com.pushsoft.myapp MyApp 之后打包的程序安装包apk的名字是"MyApp.apk",显示在手机桌面上的程 ...