生产者-消费者模型是进程间通信的重要内容之一。其原理十分简单,但自己用语言实现往往会出现很多的问题,下面我们用一系列代码来展现在编码中容易出现的问题以及最优解决方案。

/*  单生产者、单消费者生产烤鸭  */
class Resource
{
private String name;
private int count = 1;  //计数器,记录有多少只烤鸭被生产及消费
private boolean flag = false;  //停止标记
public synchronized void set(String name)  //生产烤鸭方法
{
while(flag)  //如果flag=true,则等待
{
try{this.wait();}catch(InterruptedException e){}  //使用wait()方法必须要捕捉异常
}
this.name = name + count;
count++;
System.out.println(Thread.currentThread().getName()+"..."+this.name);
flag = true;  //生产完一个烤鸭就将flag设为true,等待消费者消费烤鸭
     notify();  //唤醒消费者
}
public synchronized void out()
{
while(!flag)
{
try{this.wait();}catch(InterruptedException e){}
}
System.out.println(Thread.currentThread().getName()+"........."+this.name);
flag = false;  //消费完一个烤鸭就将flag设为false,等待生产者生产烤鸭
notify();  //唤醒生产者
}
} class Producer implements Runnable
{
Resource r;  
Producer(Resource r)
{
this.r = r;
}
public void run()
{
while(true)
{
r.set("kaoya");  //生产烤鸭
}
}
} class Consumer implements Runnable
{
Resource r;
Consumer(Resource r)
{
this.r = r;
}
public void run()
{
while(true)
{
r.out();  //消费烤鸭
}
}
} public class ResourceDemo
{
public static void main(String[] args)
{
Resource r = new Resource();
Producer producer = new Producer(r);
Consumer consumer = new Consumer(r);
Thread t1 = new Thread(producer);
Thread t2 = new Thread(producer);
t1.start();
t2.start();
}
}

以上是单生产者单消费者的代码,我们来看一下运行结果:

然而正如实际情况,饭店的厨房里不可能只有一个厨子,也不可能只有一个顾客,因此只考虑单消费者单生产者是没有意义的。我们再各加入一个消费者和生产者线程。

很显然,运行中出现了死锁,程序卡住,这非常让人费解。

实际上,在加入两个线程时,整个程序的运行过程已经发生了很大的变化。起始flag为false,因此生产者线程t1可以进入同步区,完成后将flag设为true。在这种情况下,生产者线程t3会进入wait状态。与此同时,两个消费者线程t2和t4可能都会因为一开始flag为false进入wait状态。当线程t1完成整个流程准备notify()时,线程池里一共有三个线程:生产者t3,消费者t2、t4。由于notify()的工作机制是随机唤醒一个线程,最不巧的情况,如果它唤醒的是生产者线程t3,那么t3判断flag为true,又会进入wait状态。此时此刻,所有四个线程都会进入wait状态,自然地,发生了线程死锁。

那么如何解决?最简单的方法是将notify()改为notifyAll(),这样每一次都会唤醒所有的线程。然而这种方法实在是有一点浪费资源,因为本来我们只需要唤醒1个或2个线程,而使用notifyAll()则必须将三个线程都唤醒。面试进行到这里,肯定会有一个问题——如何优化?

我们可以使用JDK1.5的特性Lock来处理,在使用它时需要在头部import java.util.concurrent.locks.*;它可以实现仅唤醒消费者线程或生产者线程,赋予了程序员更多的控制权,自然也能提高程序的效率,使得不必唤醒不需要唤醒的线程。具体代码如下:

import java.util.concurrent.locks.*;

class Resource
{
private String name;
private int count = 1;
private boolean flag = false;
Lock lock = new ReentrantLock();  //新建锁对象
Condition producer_con = lock.newCondition();  //新建生产者condition
Condition consumer_con = lock.newCondition();  //新建消费者condition
public void set(String name)
{
lock.lock();  //上锁
try
{
while(flag)
{
try{producer_con.await();}catch(InterruptedException e){}  //注意在这种情况下要将原来的this.wait()改为producer_con.await
}
this.name = name + count;
count++;
System.out.println(Thread.currentThread().getName()+"..."+this.name);
flag = true;
consumer_con.signal();  //唤醒消费者线程
}
finally
{
lock.unlock();  //把解锁放在finally里,不管中间是否有异常一定要解锁
}
}
public void out()
{
lock.lock();
try
{
while(!flag)
{
try{consumer_con.await();}catch(InterruptedException e){}
}
System.out.println(Thread.currentThread().getName()+"........."+this.name);
flag = false;
producer_con.signal();  //唤醒生产者线程
}
finally
{
lock.unlock();
}
}
} class Producer implements Runnable
{
Resource r;
Producer(Resource r)
{
this.r = r;
}
public void run()
{
while(true)
{
r.set("kaoya");
}
}
} class Consumer implements Runnable
{
Resource r;
Consumer(Resource r)
{
this.r = r;
}
public void run()
{
while(true)
{
r.out();
}
}
} public class ResourceDemo
{
public static void main(String[] args)
{
Resource r = new Resource();
Producer producer = new Producer(r);
Consumer consumer = new Consumer(r);
Thread t1 = new Thread(producer);
Thread t2 = new Thread(consumer);
Thread t3 = new Thread(producer);
Thread t4 = new Thread(consumer);
t1.start();
t2.start();
t3.start();
t4.start();
}
}

运行结果,如丝般顺滑:

Java实现多线程生产者消费者模型及优化方案的更多相关文章

  1. 第23章 java线程通信——生产者/消费者模型案例

    第23章 java线程通信--生产者/消费者模型案例 1.案例: package com.rocco; /** * 生产者消费者问题,涉及到几个类 * 第一,这个问题本身就是一个类,即主类 * 第二, ...

  2. java+反射+多线程+生产者消费者模式+读取xml(SAX)入数据库mysql-【费元星Q9715234】

    java+反射+多线程+生产者消费者模式+读取xml(SAX)入数据库mysql-[费元星Q9715234] 说明如下,不懂的问题直接我[费元星Q9715234] 1.反射的意义在于不将xml tag ...

  3. Java里的生产者-消费者模型(Producer and Consumer Pattern in Java)

    生产者-消费者模型是多线程问题里面的经典问题,也是面试的常见问题.有如下几个常见的实现方法: 1. wait()/notify() 2. lock & condition 3. Blockin ...

  4. Python多线程-生产者消费者模型

    用多线程和队列来实现生产者消费者模型 # -*- coding:utf-8 -*- __author__ = "MuT6 Sch01aR" import threading imp ...

  5. [多线程] 生产者消费者模型的BOOST实现

    说明 如果 使用过程中有BUG 一定要告诉我:在下面留言或者给我邮件(sawpara at 126 dot com) 使用boost::thread库来实现生产者消费者模型中的缓冲区! 仓库内最多可以 ...

  6. java实现多线程生产者消费者模式

    1.概念 生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题.生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消 ...

  7. java并发之生产者消费者模型

    生产者和消费者模型是操作系统中经典的同步问题.该问题最早由Dijkstra提出,用以演示它提出的信号量机制. 经典的生产者和消费者模型的描写叙述是:有一群生产者进程在生产产品.并将这些产品提供给消费者 ...

  8. Java实现多线程生产者消费者模式的两种方法

    生产者消费者模式:生产者和消费者在同一时间段内共用同一存储空间,生产者向空间里生产数据,而消费者取走数据.生产者生产一个,消费者消费一个,不断循环. 第一种实现方法,用BlockingQueue阻塞队 ...

  9. Java多线程15:Queue、BlockingQueue以及利用BlockingQueue实现生产者/消费者模型

    Queue是什么 队列,是一种数据结构.除了优先级队列和LIFO队列外,队列都是以FIFO(先进先出)的方式对各个元素进行排序的.无论使用哪种排序方式,队列的头都是调用remove()或poll()移 ...

随机推荐

  1. 201772020113 李清华《面向对象程序设计(java)》第十五周学习总结

    1.实验目的与要求 (1) 掌握Java应用程序的打包操作: (2) 了解应用程序存储配置信息的两种方法: (3) 掌握基于JNLP协议的java Web Start应用程序的发布方法: (5) 掌握 ...

  2. 如何在vs2015中编译并配置tesseract4.0

    1)安装相关软件: 下载ccpan,把路径放到path(右击电脑,选择“属性”,选择左边的“高级系统设置”,选择“环境变量”,找到“系统变量”里面的“path”,点击“编辑”,选择右边的“新建”,输入 ...

  3. 异常:Instantiation of bean failed; nested exception is java.lang.NoSuchMethodError: com.google.common.base.Preconditions.che ckState(ZLjava/lang/String;I)V

    Instantiation of bean failed; nested exception is java.lang.NoSuchMethodError: com.google.common.bas ...

  4. dataTable转换成Json格式

    #region dataTable转换成Json格式 /// <summary> /// /// </summary> /// <param name="dt& ...

  5. (转)flutter 新状态管理方案 Provide (一)-使用

    flutter 新状态管理方案 Provide (一)-使用     版权声明:本文为博主原创文章,基于CC4.0协议,首发于https://kikt.top ,同步发于csdn,转载必须注明出处! ...

  6. linux程序一般设置

    看canal的时候看了下deploy的脚本,发现很多系统都大同小异 如果是需要debug,就需要开启调试端口 if [ "$1" = "debug" ]; th ...

  7. 使用==操作符比较命令行参数args[0]和字符串返回“Invalid type"

    运行程序接收一个来自命令行的字符串参数(取值1,2,3,4),根据参数执行对应语句块. 由于未能判断字符串内容是否相同导致代码if语句块代码失效,怎么也看不到schedule方法的效果, 以下是错误代 ...

  8. MySQL 权限相关

    # ============================= mysql 权限相关 =====================================================gran ...

  9. MVC ScriptBundle自定义排序。

    今天发现MVC的ScriptBundle @Scripts.Render()后是按照我也不知道顺序显示在页面上的,后果就是jquery.min.js被排在了后面(反正我下面那堆默认jquery.min ...

  10. 使用pycharm 出现 interpreter field is empty 完美解决方法(转载 记录)

    使用pycharm 出现 interpreter field is empty 主要是因为你的电脑没有正确安装python或者安装python出错,重新下载安装覆盖就行 下载安装包:从Python的官 ...