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

/*  单生产者、单消费者生产烤鸭  */
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. lnmp环境一键安装

    lnmp一键安装命令: wget -c http://soft.vpser.net/lnmp/lnmp1.5.tar.gz && tar zxf lnmp1.5.tar.gz & ...

  2. Fiddler手机抓包设置

    前提条件:1).电脑需要安装Fiddler2).测试手机需要支持Wifi3).测试手机与电脑需要同一网络4).所测APP需支持代理 三.设置Fiddler 1.(1)电脑端打开安装好的的fiddler ...

  3. [Tools] 一种调试 Android App 接口的方式 (Fiddler/Wireshark)

    要求:Windows电脑与手机在同一局域网内(Wifi). [ Fiddler 设置代理 ] 下载地址:https://www.telerik.com/download/fiddler 依次 Tool ...

  4. 三个猜数字游戏代码(Python)

    def binary_search(list,item): low = 0 high = len(list)-1 while low <= high: mid = (low + high)//2 ...

  5. hadoop streaming 中跑python程序,自定义模块的导入

    今天在做代码重构,以前将所有python文件放到一个文件夹下,上传到hadoop上跑,没有问题:不过随着任务的复杂性增加,感觉这样甚是不合理,于是做了个重构,建了好几个包存放不同功能的python文件 ...

  6. pandas,pd.ExcelWriter保存结果到已存在的excel文件中

    背景:pandas支持将DataFrame数据直接保存到excel中   保存的case如下: import pandas as pd with pd.ExcelWriter('a.xls') as ...

  7. React Router的Route的使用

    Route 是 React Router中用于配置路由信息的组件,每当有一个组件需要根据 URL 决定是否渲染时,就需要创建一个 Route. 1) path 每个 Route 都需要定义一个 pat ...

  8. python--第十八天总结(Django进阶)

    一.路由系统 1.每个路由规则对应一个view中的函数 1 2 3 url(r'^index/(\d*)', views.index), url(r'^manage/(?P<name>\w ...

  9. CentOS 6安装配置mongodb

    安装过程 服务器下载安装包 下载: curl -O https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel62-4.0.6.tgz; 解压 ...

  10. QT Designer基础——登录界面设计基础版2

    认识QT Designer提供的可选控件:以下八个大类 Layouts:布局相关 Spacers:留空 Buttons:可点击的按钮类 Item Views和 Item Widgets:高级控件,例如 ...