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

/*  单生产者、单消费者生产烤鸭  */
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. idftp

    No FTP list parsers have been registered use IdAllFTPListParsers IdFTP1.List(LS); 中文目录乱码 2个步骤解决 use ...

  2. group_concat_max_len

    SET SESSION group_concat_max_len = 1000000; SELECT GROUP_CONCAT(a.label_number SEPARATOR ',') FROM ( ...

  3. Matplotlib-画图种类

    Scatter 散点图 本节我们将讲述各种不同的plot的方式.之前我们讲到了如何plot线,今天我们讲述如何plot散点图. # 首先,先引入matplotlib.pyplot简写作plt,再引入模 ...

  4. python之路:用户输入(一)

    python之路:用户输入(一) 好了,现在我学了点博客的用法,现在不会像以前的那么土了.好吧,不多说,我要讲课了. 今天,我会用情景实例给大家说这个用户输入. 情景是:你是IT教育的python带头 ...

  5. 执行makemigrations后错误集锦

    在项目配置xadmin后,执行python manage.py makemigrations后出现了很多问题: 1.ModuleNotFoundError: No module named 'futu ...

  6. 用Nginx+Lua+Redis给百度鹰眼API服务搭建缓存服务中间件(记录过程)

    一.环境安装部分 Centos7,Nginx1.14,Redis5.0,luajit-2.1,ngx_devel_kit-0.3.1rc1,lua-nginx-module-0.10.14. 下载安装 ...

  7. 将对象序列化成XML字符串

    using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.We ...

  8. CentOS7下Mysql 5.6.30安装与配置

    环境:centos 7 x64 先下载mysql安装包 打开 http://dev.mysql.com/downloads/mysql/  选择 linux - Generic 再选择 下载完毕后,得 ...

  9. redis+Keepalived主从热备切换实例

    [root@cache-redis-~/]# cat /etc/keepalived/keepalived.conf ! Configuration File for keepalived globa ...

  10. IDEA配置

    关于IDEA的配置 配置注释模板 CTRL_SHIFT_S,在Live Templates中新增一个TemplateGroup,然后再新建两个模板,如下图: 新增cc-ClassComment /** ...