生产者与消费者(一)---wait与notify
生产者消费者问题是研究多线程程序时绕不开的经典问题之一,它描述是有一块缓冲区作为仓库,生产者可以将产品放入仓库,消费者则可以从仓库中取走产品。解决生产者/消费者问题的方法可分为两类:
(1)采用某种机制保护生产者和消费者之间的同步;
(2)在生产者和消费者之间建立一个管道。
第一种方式有较高的效率,并且易于实现,代码的可控制性较好,属于常用的模式。第二种管道缓冲区不易控制,被传输数据对象不易于封装等,实用性不强。因此本文只介绍同步机制实现的生产者/消费者问题。
同步问题核心在于:如何保证同一资源被多个线程并发访问时的完整性。常用的同步方法是采用信号或加锁机制,保证资源在任意时刻至多被一个线程访问。Java语言在多线程编程上实现了完全对象化,提供了对同步机制的良好支持。在Java中一共有四种方法支持同步,其中前三个是同步方法,一个是管道方法。
(1)wait() / notify()方法
(2)await() / signal()方法
(3)BlockingQueue阻塞队列方法
(4)PipedInputStream / PipedOutputStream
本文先介绍第一种wait() / notify()方法,其他三种后面再讨论。
wait() / notify()方法
wait() / nofity()方法是基类Object的两个方法,也就意味着所有Java类都会拥有这两个方法,这样,我们就可以为任何对象实现同步机制。
wait()方法:当缓冲区已满/空时,生产者/消费者线程停止自己的执行,放弃锁,使自己处于等待状态,让其他线程执行。
notify()方法:当生产者/消费者向缓冲区放入/取出一个产品时,向其他等待的线程发出可执行的通知,同时放弃锁,使自己处于等待状态。
代码示例:
缓冲区(仓库):
public class Storage {
// 仓库最大存储量
private final int MAX_SIZE = 100;
// 仓库存储的载体
private LinkedList<Object> list = new LinkedList<Object>();
// 生产num个产品
public void produce(int num){
// 同步代码段
synchronized (list){
// 如果仓库剩余容量不足
while (list.size() + num > MAX_SIZE){
System.out.println("【要生产的产品数量】:" + num + "/t【库存量】:"+ list.size() + "/t暂时不能执行生产任务!");
try{
// 由于条件不满足,生产阻塞
list.wait();
}
catch (InterruptedException e){
e.printStackTrace();
}
}
// 仓库剩余容量充足,即生产条件满足情况下,生产num个产品
for (int i = 1; i <= num; ++i){
list.add(new Object());
}
System.out.println("【已经生产产品数】:" + num + "/t【现仓储量为】:" + list.size());
list.notifyAll(); //生产完产品后,通知其他被阻塞的线程
}
}
// 消费num个产品
public void consume(int num){
// 同步代码段
synchronized (list){
// 如果仓库存储量不足
while (list.size() < num){
System.out.println("【要消费的产品数量】:" + num + "/t【库存量】:"+ list.size() + "/t暂时不能执行消费任务!");
try{
// 由于条件不满足,消费阻塞
list.wait();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
// 消费条件满足情况下,消费num个产品
for (int i = 1; i <= num; ++i){
list.remove();
}
System.out.println("【已经消费产品数】:" + num + "/t【现仓储量为】:" + list.size());
list.notifyAll();//消费完后,释放锁,通知其他被阻塞的线程
}
}
// get/set方法
public LinkedList<Object> getList(){
return list;
}
public void setList(LinkedList<Object> list){
this.list = list;
}
public int getMAX_SIZE(){
return MAX_SIZE;
}
}
生产者:
public class Producer extends Thread {
// 每次生产的产品数量
private int num;
// 所在放置的仓库
private Storage storage;
// 构造函数,设置仓库
public Producer(Storage storage){
this.storage = storage;
}
// 线程run函数
public void run(){
produce(num);
}
// 调用仓库Storage的生产函数
public void produce(int num){
storage.produce(num);
}
// get/set方法
public int getNum()
{
return num;
}
public void setNum(int num)
{
this.num = num;
}
public Storage getStorage()
{
return storage;
}
public void setStorage(Storage storage)
{
this.storage = storage;
}
}
消费者:
public class Consumer extends Thread {
// 每次消费的产品数量
private int num;
// 所在放置的仓库
private Storage storage;
// 构造函数,设置仓库
public Consumer(Storage storage){
this.storage = storage;
}
// 线程run函数
public void run(){
consume(num);
}
// 调用仓库Storage的生产函数
public void consume(int num){
storage.consume(num);
}
// get/set方法
public int getNum()
{
return num;
}
public void setNum(int num)
{
this.num = num;
}
public Storage getStorage()
{
return storage;
}
public void setStorage(Storage storage)
{
this.storage = storage;
}
}
测试类:
public class Test {
public static void main(String[] args) {
// 仓库对象
Storage storage = new Storage();
// 生产者对象
Producer p1 = new Producer(storage);
Producer p2 = new Producer(storage);
Producer p3 = new Producer(storage);
Producer p4 = new Producer(storage);
Producer p5 = new Producer(storage);
Producer p6 = new Producer(storage);
Producer p7 = new Producer(storage);
// 消费者对象
Consumer c1 = new Consumer(storage);
Consumer c2 = new Consumer(storage);
Consumer c3 = new Consumer(storage);
// 设置生产者产品生产数量
p1.setNum(10);
p2.setNum(10);
p3.setNum(10);
p4.setNum(10);
p5.setNum(10);
p6.setNum(10);
p7.setNum(80);
// 设置消费者产品消费数量
c1.setNum(50);
c2.setNum(20);
c3.setNum(30);
// 线程开始执行
c1.start();
c2.start();
c3.start();
p1.start();
p2.start();
p3.start();
p4.start();
p5.start();
p6.start();
p7.start();
}
}
你可能会对Storage类中为什么要定义public void produce(int num);和public void consume(int num);方法感到不解,为什么不直接在生产者类Producer和消费者类Consumer中实现这两个方法,却要调用Storage类中的实现呢?
因为如果在需要更新生产或者消费方法时,只需要更新仓库类Storage的代码即可,生产者Producer、消费者Consumer、测试类Test的代码均不需要进行任何更改。这样我们就知道为什么要在Storage类中定义public void produce(int num);和public void consume(int num);方法,并在生产者类Producer和消费者类Consumer中调用Storage类中的实现了吧。将可能发生的变化集中到一个类中,不影响原有的构架设计,同时无需修改其他业务层代码。无意之中,好像使用了一种叫做策略模式的设计模式!
生产者与消费者(一)---wait与notify的更多相关文章
- JAVA并发实现五(生产者和消费者模式wait和notify方式实现)
package com.subject01; import java.util.PriorityQueue; /** * 通过wait和notify 实现 * 生产者-消费者模型:当队列满时,生产者需 ...
- 生产者与消费者模式-阻塞 wait,notify
设计思路:生产者push ,消费者 拿,篮子装,syncstack先进后出,while 判断 index=0 wait, 当 Producer生产了 并push到篮子里 notify(唤醒 ...
- 【java线程系列】java线程系列之线程间的交互wait()/notify()/notifyAll()及生产者与消费者模型
关于线程,博主写过java线程详解基本上把java线程的基础知识都讲解到位了,但是那还远远不够,多线程的存在就是为了让多个线程去协作来完成某一具体任务,比如生产者与消费者模型,因此了解线程间的协作是非 ...
- Thread(生产者和消费者) wait、notify、notifyAll
在java中,线程间的通信可以使用wait.notify.notifyAll来进行控制.从名字就可以看出来这3个方法都是跟多线程相关的,但是可能让你感到吃惊的是:这3个方法并不是Thread类或者是R ...
- 母鸡下蛋实例:多线程通信生产者和消费者wait/notify和condition/await/signal条件队列
简介 多线程通信一直是高频面试考点,有些面试官可能要求现场手写生产者/消费者代码来考察多线程的功底,今天我们以实际生活中母鸡下蛋案例用代码剖析下实现过程.母鸡在鸡窝下蛋了,叫练从鸡窝里把鸡蛋拿出来这个 ...
- java进阶(40)--wait与notify(生产者与消费者模式)
文档目录: 一.概念 二.wait的作用 三.notify的作用 四.生产者消费者模式 五.举例 ---------------------------------------分割线:正文------ ...
- 第3月第2天 find symbolicatecrash 生产者-消费者 ice 引用计数
1.linux find export find /Applications/Xcode.app/ -name symbolicatecrash -type f export DEVELOPER_DI ...
- java 22 - 19 多线程之生产者和消费者的代码优化
在之前,是把生产者录入数据和消费者获取数据的所有代码都分别写在各自的类中. 这样不大好 这次把生产者和消费者部分关键代码都写入资源类中: package zl_Thread; public class ...
- 线程操作案例--生产者与消费者,Object类对线程的支持
本章目标 1)加深对线程同步的理解 2)了解Object类中对线程的支持方法. 实例 生产者不断生产,消费者不断消费产品. 生产者生产信息后将其放到一个区域中,之后消费者从区域中取出数据. 既然生产的 ...
随机推荐
- Java---俄罗斯方块小游戏
去年就已经学了这个技术了,一直没去写,现在抽个时间写了个俄罗斯方块游戏. 只有简单的新游戏,暂停,继续,积分功能.简单的实现了俄罗斯的经典功能. 不介绍了,有兴趣的自己运行一下,后面贴出了图片. 代码 ...
- Android SlidingMenu 滑出侧边栏
最近有个项目需要使用侧边栏,而且希望是左右两侧都能够滑出侧边菜单,在网上查找实现方式时,发现大家用的最多的还是大神jfeinstein10的SlidingMenu库,地址https://github. ...
- Java中重载和重写的区别
重载 overloading 1) 方法重载是让类以统一的方式处理不同类型数据的一种手段.多个同名函数同时存在,具有不同的参数个数/类型.重载是一个类中多态性的一种表现. 2) Java的方法重载,就 ...
- (DT系列二)device tree的书写规范
devicetree的书写规范 下面从节点,属性,reg,ranges,中断控制器等几个方面叙述devicetree的书写规范. 1,dts的基本元素:节点 .dts(或者其include的.dtsi ...
- Eclipse修改字体大小
1.MyEclipse|Window|General|Appearance|Colors and Fonts->点击Text Font->Edit
- DevExpress 用户控件 分页(中)
说明: 1)具体调用请关注 看DevExpress 用户控件 分页(下) datanavi_ButtonClick 是DataNavigator的ButtonClikc事件 视图设计器: 分页用户控件 ...
- 在IDE中用Bing Code Search直接查找代码片段并且插入
博客搬到了fresky.github.io - Dawei XU,请各位看官挪步.最新的一篇是:在IDE中用Bing Code Search直接查找代码片段并且插入.
- java消息队列使用场景
http://blog.163.com/sir_876/blog/static/11705223201332444647261/ 目前能用到的比较不错的消息队列组件 ,kafka,activeMq, ...
- Jakarta-Commons- BeanUtils学习笔记:
http://www.cnblogs.com/zhangyi85/archive/2009/04/22/1441341.html 1.什么是BeanUtils: BeanUtils主要提供了对于Jav ...
- UVA Mapping the Swaps
题目例如以下: Mapping the Swaps Sorting an array can be done by swapping certain pairs of adjacent entrie ...