*java多线程--等待唤醒机制:经典的体现"生产者和消费者模型
 *对于此模型,应该明确以下几点:
 *1.生产者仅仅在仓库未满的时候生产,仓库满了则停止生产。
 *2.消费者仅仅在有产品的时候才能消费,仓空则等待。
 *3.当消费者发现仓储没有产品可消费的时候,会唤醒等待生产者生产。
 *4.生产者在生产出可以消费的产品的时候,应该通知等待的消费者去消费。

下面先介绍个简单的生产者消费者例子:本例只适用于两个线程,一个线程生产,一个线程负责消费。

生产一个资源,就得消费一个资源。

代码如下:

public class ProduceOneCusumer {

    /**
* @param args
*/
public static void main(String[] args) {
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
Thread t1 = new Thread(pro);//生产者线程
Thread t2 = new Thread(con);//消费者线程
t1.start();
t2.start(); } }
/**
* 仓储类,仓储原料
* @author Administrator
*
*/
class Resource{
private String name;//原料名称
private int count = 1;//生产数量
private boolean flag = false; public synchronized void set(String name){//t1
if(flag){
try {
this.wait();//仓库满了,生产者等待,等待消费者消费
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = name+"------"+count++;
System.out.println(Thread.currentThread().getName()+"..生产者.."+this.name);
this.flag = true;
this.notify();//唤醒消费者线程进行消费 }
public synchronized void out(){//t2,t3
if(!flag){//false
try {
this.wait();//仓库为空不能消费,消费者线程等待,等待生产者生产
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"......消费者......."+this.name);
this.flag = false;
this.notify();//唤醒生产者
}
} /**
* 生产者类
* @author Administrator
*
*/
class Producer implements Runnable{
private Resource r;
public Producer(Resource r){//把原料放到生产者里面生产,当生产者一创建生产对象,就初始化原料
this.r = r;
}
@Override
public void run() {
while(true){//不断的生产
r.set("烤鸭");//生产烤鸭
}
}
} class Consumer implements Runnable{
private Resource r;
public Consumer(Resource r){
this.r = r;
} @Override
public void run() {
while(true){
r.out();//消费者消费
}
} }

输出结果:

Thread-1......消费者.......烤鸭------21688
Thread-0..生产者..烤鸭------21689
Thread-1......消费者.......烤鸭------21689
Thread-0..生产者..烤鸭------21690
Thread-1......消费者.......烤鸭------21690
Thread-0..生产者..烤鸭------21691
Thread-1......消费者.......烤鸭------21691
Thread-0..生产者..烤鸭------21692
Thread-1......消费者.......烤鸭------21692

从结果可以得知,生产者线程生产一只烤鸭,消费者就消费一只烤鸭。两个线程交替等待唤醒。

下面我们把上面代码稍微改动下:再增加两个线程,变为两个生产者线程分别为t0,t1,两个消费者线程,分比为t3,t4.改动Main方法,其它不变。

代码如下:

public class ProduceOneCusumer {

    /**
* @param args
*/
public static void main(String[] args) {
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
Thread t0 = new Thread(pro);//生产者线程
Thread t1 = new Thread(pro);//生产者线程
Thread t2 = new Thread(con);//消费者线程
Thread t3 = new Thread(con);//消费者线程
t0.start();
t1.start();
t2.start();
t3.start(); } }

则经过测试代码运行结果:会发现:

              第一种情况:生产者线程一直在生产,仓库满了也不消费;

第二种情况:生产了一个 ,消费了多次,也就是说库存里面没有烤鸭了还在消费。

结果如下:

第一种情况:

Thread-0..生产者..烤鸭------28348
Thread-1..生产者..烤鸭------28349
Thread-0..生产者..烤鸭------28350
Thread-1..生产者..烤鸭------28351
Thread-0..生产者..烤鸭------28352
Thread-1..生产者..烤鸭------28353
Thread-0..生产者..烤鸭------28354
Thread-1..生产者..烤鸭------28355
Thread-0..生产者..烤鸭------28356
Thread-1..生产者..烤鸭------28357
Thread-0..生产者..烤鸭------28358
Thread-2......消费者.......烤鸭------28358

第二种情况:

Thread-0..生产者..烤鸭------29432
Thread-2......消费者.......烤鸭------29432
Thread-3......消费者.......烤鸭------29432
Thread-2......消费者.......烤鸭------29432
Thread-3......消费者.......烤鸭------29432
Thread-2......消费者.......烤鸭------29432

结论:多个线程会出现问题,思考原因?

这里分析代码解释第一种情况,第二种情况和第一种情况类试。

代码的执行过程分析:

     假设有两个生产线程为t0,t1;两个消费线程为t1,t2。假设开始生产者t0得到了CPU的执行权,进入生产者run方法,拿到this锁,进入set方法,初始值flag = false;所以不走if中的代码,走else中的代码,则生产者生产了一只烤鸭(打印出"Thread-0..生产者..烤鸭1"),thread-0执行完剩余的代码,释放this锁。

这个时候,因为thread-0是执行完run方法中的set方法中的所有代码,释放锁,并没有执行wait方法,那么线程0还可能抢到CUP资源,拿到 锁,再次进入Run方法中的set方法,进入判断if(flag=true),所以线程0(Thread-0)等待。

这时活跃的线程还有t1,t2,t3;一个生产线程,两个消费线程.这个时候还有可能t1抢到CPU的执行权, t1拿到this锁,进入set方法,if(true),那么执行wait,这个时候t1(Thread-1)也等待。

这个时候:还有两个线程活着,t2,t3。假设t2抢到CPU的执行权,在消费者run方法里面运行,执行out函数,拿到锁进入out方法,if 判断,由于上面flag=true;所以不执行if代码,执行else代码,则消费者消费一只烤鸭(打印Thread-2....消费者.....烤鸭1),紧跟着
 执行t2执行剩余的代码,flag=false;notify()。

这个时候等待线程池中有2个线程处于冻结状态,分别为t0,t1。当执行notify()方法的时候,“就可以唤醒同锁中等待的线程中的一个”。可能是t1,也可能是t2。假设t1被唤醒,这时活跃的线程还包括t2,t3,t0.假设还是t2抢到CPU执行权,t2进入out,由于flag被置为flase,那么if判断,这个时候t2执行wait方法,等待。这个时候剩余t0,t3是活着的,假设t3抢到执行权,拿到锁进入out方法,判断,t3也等待。这个时候还有一个线程处于活跃状态,那就是t0.

关键就是这个地方:四个线程t1,t2,t3都执行了wait方法进入等待状态,t0那会儿执行wait,刚被notify唤醒,唤醒之后,直接执行
else里面的代码,这时候生产者生产了一只烤鸭(打印出"Thread-0..生产者..烤鸭2"),flag=true,这个时候执行notify,最悲剧的是有可能唤醒了t1,和t0一样的生产者。这个时候两个
生产者线程活着。为t0,t1,可能t0抢到CPU执行权,进入set,if判断,执行wait,t0等待,这个时候活过来的只剩t1,由于t1是从
wait中活过来的,所以不用if判断,执行else中的代码,生产者生产了一只烤鸭(打印出"Thread-1..生产者..烤鸭3")。这样flag=true,notify,又有可能唤醒t0,t2,t3中的
t1.这样可能形成一个规律就是只生产,不消费。就会导致上面的结果。

    造成的根本原因是:if只判断一次,当等待的线程醒过来以后,没有执行if判断,那么使用while循环就可以解决这个问题,等待的线程醒过来之后还会判断里面的条件。

代码变成:把if改为while:

     

public class ProduceOneCusumer {

    /**
* @param args
*/
public static void main(String[] args) {
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
Thread t0 = new Thread(pro);//生产者线程
Thread t1 = new Thread(pro);//生产者线程
Thread t2 = new Thread(con);//消费者线程
Thread t3 = new Thread(con);//消费者线程
t0.start();
t1.start();
t2.start();
t3.start(); } }
/**
* 仓储类,仓储原料
* @author Administrator
*
*/
class Resource{
private String name;//原料名称
private int count = 1;//生产数量
private boolean flag = false; public synchronized void set(String name){//t1
while(flag){
try {
this.wait();//仓库满了,生产者等待,等待消费者消费
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = name+"------"+count++;
System.out.println(Thread.currentThread().getName()+"..生产者.."+this.name);
this.flag = true;
this.notify();//唤醒消费者线程进行消费 }
public synchronized void out(){//t2,t3
while(!flag){//false
try {
this.wait();//仓库为空不能消费,消费者线程等待,等待生产者生产
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"......消费者......."+this.name);
this.flag = false;
this.notify();//唤醒生产者
}
} /**
* 生产者类
* @author Administrator
*
*/
class Producer implements Runnable{
private Resource r;
public Producer(Resource r){//把原料放到生产者里面生产,当生产者一创建生产对象,就初始化原料
this.r = r;
}
@Override
public void run() {
while(true){//不断的生产
r.set("烤鸭");//生产烤鸭
}
}
} class Consumer implements Runnable{
private Resource r;
public Consumer(Resource r){
this.r = r;
} @Override
public void run() {
while(true){
r.out();//消费者消费
}
} }

测试结果:不会出现上面的那两种情况了,但可能出现死锁。

Thread-0..生产者..烤鸭------1
Thread-2......消费者.......烤鸭------1
Thread-1..生产者..烤鸭------2
Thread-2......消费者.......烤鸭------2
Thread-0..生产者..烤鸭------3
Thread-2......消费者.......烤鸭------3
Thread-0..生产者..烤鸭------4
Thread-2......消费者.......烤鸭------4
Thread-0..生产者..烤鸭------5
Thread-3......消费者.......烤鸭------5
Thread-0..生产者..烤鸭------6
Thread-2......消费者.......烤鸭------6

死锁原因:

当改成while循环后,由于刚上面的分析t1从等待中唤醒,t0,t2,t3等待了,由于flag=true,改成while循环后,会再次判断标志,while条件成立,则t1执行wait方法,等待。这个时候t0,t1,t2,t3都处于等待状态,没有线程唤醒,那么出现了死锁。简单的说就是四个线程都等待,没有活着的线程,程序出现死锁,不会循环生产和消费。

那么怎么解决呢:把notify改为notifyAll方法,出现的原因是每次只能任意的唤醒一个线程,当改成notifyAll方法后可以唤醒所有等待的线程。

最终多个线程的消费者生产者程序问题全部解决。

总结:两个线程用if判断可以,多个线程的生产者消费者问题用while循环判断标志。在分析的过程中我们可以理解wait,notify和notifyAll的用法,三个方法都用在同一个资源当中的同一个锁中,成对出现,必须用在同步(锁)当中,否则会出现IllegalMonitorStateException - 如果当前线程不是此对象监视器(锁)的所有者异常

 

java基础知识回顾之java Thread类学习(八)--java多线程通信等待唤醒机制经典应用(生产者消费者)的更多相关文章

  1. java基础知识回顾之java Thread类学习(七)--java多线程通信等待唤醒机制(wait和notify,notifyAll)

    1.wait和notify,notifyAll: wait和notify,notifyAll是Object类方法,因为等待和唤醒必须是同一个锁,不可以对不同锁中的线程进行唤醒,而锁可以是任意对象,所以 ...

  2. java基础知识回顾之---java String final类普通方法

    辞职了,最近一段时间在找工作,把在大二的时候学习java基础知识回顾下,拿出来跟大家分享,如果有问题,欢迎大家的指正. /*     * 按照面向对象的思想对字符串进行功能分类.     *      ...

  3. Java基础知识回顾之七 ----- 总结篇

    前言 在之前Java基础知识回顾中,我们回顾了基础数据类型.修饰符和String.三大特性.集合.多线程和IO.本篇文章则对之前学过的知识进行总结.除了简单的复习之外,还会增加一些相应的理解. 基础数 ...

  4. Java基础-进程与线程之Thread类详解

    Java基础-进程与线程之Thread类详解 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.进程与线程的区别 简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程 ...

  5. java 22 - 17 多线程之等待唤醒机制(接16)

    先来一张图,看看什么叫做等待唤醒机制 接上一章的例子. 例子:学生信息的录入和获取 * 资源类:Student  * 设置学生数据:SetThread(生产者) * 获取学生数据:GetThread( ...

  6. Java基础知识回顾(一):字符串小结

    Java的基础知识回顾之字符串 一.引言 很多人喜欢在前面加入赘述,事实上去技术网站找相关的内容的一般都应当已经对相应知识有一定了解,因此我不再过多赘述字符串到底是什么东西,在官网中已经写得很明确了, ...

  7. Java 之多线程通信(等待/唤醒)

    多线程间通信: 多个线程在处理同一个资源, 但是任务却不同. 等待/唤醒机制 涉及的方法 wait(): 让线程处于冻结状态, 被 wait() 的线程会被存储到线程池中 notify(): 唤醒线程 ...

  8. Java第二十五天,多线程之等待唤醒机制

    当线程被创建并且被启动之后,它既不是一启动就进入了执行状态,也不是一直处于执行状态,而是具有以下多种状态: 这六种状态之间的转换关系如下: 1.等待唤醒机制 注意: (1)两个线程之间必须用同步代码块 ...

  9. java基础知识回顾之java Thread类学习(八)--java.util.concurrent.locks(JDK1.5)与synchronized异同讲解

    看API文档介绍几个方法:  JDK1.5中提供了多线程的升级解决方案: 特点: 1.将同步synchronized显示的替换成Lock                    2.接口Conditio ...

随机推荐

  1. 利用word2vec对关键词进行聚类

    1.收集预料 自己写个爬虫去收集网页上的数据. 使用别人提供好的数据http://www.sogou.com/labs/dl/ca.html 2.对预料进行去噪和分词 我们需要content其中的值, ...

  2. 在Windows Phone中使用HTML编程

    在开发Windows Phone的项目中,需求中有几个页面是要用表格来布局的(效果图如下),由于Grid中有的边线是虚的,而且没有边线,果断放弃了,用了border将表格的线加上去了.于是在有表格布局 ...

  3. FIR滤波器(1)- 基础知识

    FIR滤波器广泛应用于数字信号处理中,主要功能就是将不感兴趣的信号滤除,留下有用信号.FIR滤波器是全零点结构,系统永远稳定:并且具有线性相位的特征,在有效频率范围内所有信号相位上不失真.在无线通信收 ...

  4. spring MVC项目中,欢迎页首页根路径到底是怎么设置的

    0. 问题: 如何改mvc中项目的欢迎页,或者叫做根路径 一个东西快弄完了,就剩下一个问题,应该是个小问题.就是mvc项目的欢迎页,怎么给改下呢. 这个项目是通过mvn建立的,整个项目的原型就是spr ...

  5. EasyUI DataGrid分页数据绑定

    记录东西感觉很痛苦,总结东西很痛苦,麻烦,不过为了下次的方便和知识的牢固以后要坚持总结. EasyUI DataGrid分页数据绑定 在解决方案中新建两个文件FormMain.aspx(html也可以 ...

  6. C# 在子线程中创建不会阻塞执行窗体

    可以参考”C# 对 Windows 窗体控件进行线程安全调用“一文来看. 在做网络连接程序的时候碰到一个问题:每当连接到来时,都创建一个新的接收线程,而该接收线程在接收到消息时,可以创建一个新的对话窗 ...

  7. javascript 创建对象及对象原型链属性介绍

    我们知道javascript里定义一个普通对象的方法,如: let obj = {}; obj.num = 1; obj.string = 'string'; obj.func = function( ...

  8. 常用的php数组排序函数

    分享几个php数组排序函数,每个函数出去sort是排序的意思前缀字母的含义分别代表: a 索引 k 数组键 r 逆向 u 用户自定义 顺序排序函数 sort — 对数组排序  ksort — 对数组按 ...

  9. 有关Mysql连接问题

    问题一——Mysql number Error 2003 MySQL连接错误.错误代码10061.10061一般是Mysql服务没启动.或Mysql服务器无法连接 . 在程序栏找到Mysql\Mysq ...

  10. 关于CSS中的PX值(像素)

    场景: 人物:前端实习生「阿树」与 切图工程师「玉凤」事件:设计师出设计稿,前端实现页面 玉凤:树,设计稿发给你啦,差那么点像素,就叼死你┏(  ̄へ ̄)=☞阿树:~(>_<)~毛问题噶啦~ ...