生产者-消费者问题是经典的并发问题, 非常适合并发入门的编程练习。

   生产者-消费者问题是指, 有若干个生产者和若干个消费者并发地读写一个或多个共享存储空间;生产者创建对象并放入到共享存储空间,消费者从共享存储空间取出对象进行消费处理。当共享存储空间为满时,生产者被阻塞;当共享存储空间为空时,消费者被阻塞。本文先使用一个自定义的有限长度字符序列缓冲区来作为共享存储空间,并使用原生的 wait 和 notify 机制来实现并发读写; 接着使用 Java 并发库提供的 BlockQueue 来实现同样的目的。

   一、  使用自定义的有限长度字符序列缓冲区来作为共享存储空间:

(1)  该缓冲区必须是线程安全的, 比较简单可行的是在方法上加入 synchronized 关键字;

  (2)  在并发读写该缓冲区的线程 Producer 和 Comsumer 中,要小心地将 wait 方法放在 while 循环中, 使用 notifyAll 来通知;

(3)  即使缓冲区是线程安全的,要确保操作及其展示结果一致时需要使用同步确保一起执行;

  (4)  使用了可见性变量 volatile boolean endflag 来取消线程的执行, 更好的方式是通过线程中断。

/**
* PCProblem :
* 模拟生产者-消费者问题, 生产者产生字符并写入字符序列缓冲区, 消费者从缓冲区取走字符
*
* @author shuqin1984 2011-08-05
*
*/ package threadprogramming.basic.simulation; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit; public class PCProblem { public static void main(String[] args) { System.out.println(" ---- Thread main starts up ---- "); // 模拟 生产者 - 消费者 任务 SharedCharBuffer sharedBuffer = new SharedCharBuffer(10);
ExecutorService es = Executors.newCachedThreadPool(); for (int i=1; i <= 10; i++) {
es.execute(new ProducerThread(i, sharedBuffer));
es.execute(new ConsumerThread(i, sharedBuffer));
}
es.shutdown(); // 运行 5 秒后终止模拟 try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
} ProducerThread.cancel();
ConsumerThread.cancel();
es.shutdownNow(); System.out.println("Time to be over."); } }

  生产者: Producer.java

/**
* ProducerThread: 生产者线程
*/ package threadprogramming.basic.simulation; import java.util.Random;
import java.util.concurrent.TimeUnit; public class ProducerThread extends Thread { private static String str = "abc1defg2hijk3lmno4pqrs5tuvwx6yz" +
"AB7CDEF8GHIJK9LMNO0PQR_STU*VWXYZ"; private static volatile boolean endflag = false; private final int id; private SharedCharBuffer buffer; public ProducerThread(int id, SharedCharBuffer buffer) {
this.id = id;
this.buffer = buffer;
} public static void cancel() {
endflag = true;
} public boolean isCanceled() {
return endflag == true;
} /**
* 生产者任务: 只要任务不取消,且缓冲区不满,就往缓冲区中字符
*/
public void run()
{
while (!isCanceled() && !Thread.interrupted()) {
synchronized (buffer) {
while (buffer.isFull()) {
// 缓冲区已满,生产者必须等待
try {
buffer.wait();
} catch (InterruptedException e) {
System.out.println(this + " Interrupted.");
}
}
char ch = produce();
System.out.println(TimeIndicator.getcurrTime() + ":\t" + this + " 准备写缓冲区:" + ch);
buffer.write(ch);
System.out.println(TimeIndicator.getcurrTime() + ":\t" + this + " :\t\t\t" + buffer);
buffer.notifyAll();
}
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
System.out.println(this + " Interrupted.");
}
}
System.out.println("Exit from: " + this);
} public char produce()
{
Random rand = new Random();
return str.charAt(rand.nextInt(64));
} public String toString()
{
return "P[" + id + "]";
} }

   消费者:

/**
* ConsumerThread: 消费者线程
*
*/ package threadprogramming.basic.simulation; import java.util.concurrent.TimeUnit; public class ConsumerThread implements Runnable { private static volatile boolean endflag = false; private final int id; private SharedCharBuffer buffer; public ConsumerThread(int id, SharedCharBuffer buffer) {
this.id = id;
this.buffer = buffer;
} public static void cancel() {
endflag = true;
} public boolean isCanceled() {
return endflag == true;
} /**
* consume:
* 当缓冲区buffer中有字符时,就取出字符显示【相当于消费者】。
*
*/
public char consume() {
return buffer.fetch();
} /**
* 消费者任务: 只要任务不取消,且缓冲区不被置空,就从缓冲区中取出字符消费。
*/
public void run() { while (!isCanceled() && !Thread.interrupted()) {
synchronized (buffer) {
while (buffer.isEmpty()) {
try {
buffer.wait();
} catch (InterruptedException e) {
System.out.println(this + " Interrupted.");
}
}
System.out.println(TimeIndicator.getcurrTime() + ":\t" + this + " 取出字符: " + consume());
System.out.println(TimeIndicator.getcurrTime() + ":\t" + this + " :\t\t\t" + buffer);
buffer.notifyAll();
}
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
System.out.println(this + " Interrupted.");
}
}
System.out.println("Exit from: " + this); } public String toString() {
return "C[" + id + "]";
} }

  有限字符缓冲区: SharedCharBuffer.java 

/**
* CharBuffer:
* 实现有限长度字符缓冲区的互斥读写。
*
*/ package zzz.study.threadprogramming.basic.simulation; public class CharBuffer { private final int capacity; // 指定字符缓冲区能容纳的字符数 private char[] charBuffer; // 用来生产和消费的有限长度字符缓冲区
private int index; private int count; // 该缓冲区被读写的次数,可衡量性能 public CharBuffer(int capacity) { if (charBuffer == null) {
charBuffer = new char[capacity];
}
this.capacity = capacity;
index = 0; } /**
* 判断缓冲区是否已满,满则生产者等待
*/
public boolean isFull()
{
return index == capacity;
} /**
* 判断缓冲区是否为空,空则消费者等待
*/
public boolean isEmpty()
{
return index == 0;
} /**
* write: 将给定字符写入缓冲区中【改变了缓冲区内容】
* synchronized 关键字用于实现互斥访问缓冲区
* @param ch character that will be written into the buffer.
*
*/
public synchronized void write(char ch) { charBuffer[index] = ch;
index++;
count++;
} /**
* read: 读取缓冲区中给定位置的字符【不改变缓冲区内容】
* synchronized 关键字用于实现互斥访问缓冲区
* @param index integer representation of the position
*
*/
public synchronized char read(int index) {
return charBuffer[index];
} /**
* fetch: 取出缓冲区给定位置的字符【改变了缓冲区内容】
* synchronized 关键字用于实现互斥访问缓冲区
*
*/
public synchronized char fetch() { index--;
count++;
return charBuffer[index];
} /**
* getStringOfBuffer: 缓冲区内容的字符串表示
* @return string representation of the buffer's contents
*
*/
public synchronized String toString() { if (isEmpty()) {
return "缓冲区为空!";
}
else {
StringBuilder bufferstr = new StringBuilder("缓冲区内容: ");
for (int i=0; i < index; i++) {
bufferstr.append(charBuffer[i]);
}
return bufferstr.toString();
} } public int getCount() {
return count;
} }

   二、  使用阻塞队列 BlockQueue 来实现生产者-消费者问题求解

  可以看到, 客户端代码简化了不少, 错误风险也降低了。 只要在主线程创建一个 BlockQueue, 传给生产者 ProducerUsingQueue 和 消费者 ConsumerUsingQueue , 然后直接使用 BlockQueue 提供的同步机制。BlockQueue 在内部分别使用了Condition notFull 和 notEmpty 分别来通知 生产者和消费者, 在方法实现中使用了可重入锁 ReentrantLock 来确保并发互斥的操作。    

package zzz.study.threadprogramming.basic.simulation.usequeue;

import org.apache.commons.logging.Log;
import org.apache.log4j.Logger;
import zzz.study.threadprogramming.basic.simulation.TimeIndicator; import java.util.Random;
import java.util.concurrent.BlockingQueue; public class ProducerUsingQueue extends Thread { private static String str = "abc1defg2hijk3lmno4pqrs5tuvwx6yz" +
"AB7CDEF8GHIJK9LMNO0PQR_STU*VWXYZ"; private static volatile boolean endflag = false; private final int id; BlockingQueue<Character> buffer; private Logger log = Logger.getLogger("appInfo"); public ProducerUsingQueue(int id, BlockingQueue<Character> buffer) {
this.id = id;
this.buffer = buffer;
} public static void cancel() {
endflag = true;
} public boolean isCanceled() {
return endflag == true;
} public void run()
{
while (!isCanceled()) {
try {
char ch = produce();
log.info(TimeIndicator.getcurrTime() + ":\t" + this + " 准备写缓冲区:" + ch);
buffer.put(ch);
log.info(TimeIndicator.getcurrTime() + ":\t" + this + " :\t\t\t" + buffer);
} catch (InterruptedException e) {
log.error(this + " Interrupted: " + e.getMessage());
}
}
} public char produce()
{
Random rand = new Random();
return str.charAt(rand.nextInt(64));
} public String toString()
{
return "P[" + id + "]";
} }
/**
* CharOutputThread:
* 通过创建线程,并使用CharBuffer来实现并发地读和写字符缓冲区的仿真
*
*/ package zzz.study.threadprogramming.basic.simulation.usequeue; import org.apache.log4j.Logger;
import zzz.study.threadprogramming.basic.simulation.TimeIndicator; import java.util.concurrent.BlockingQueue; public class ConsumerUsingQueue extends Thread { private static volatile boolean endflag = false; private final int id; private BlockingQueue<Character> buffer; private Logger log = Logger.getLogger("appInfo"); public ConsumerUsingQueue(int id, BlockingQueue<Character> buffer) {
this.id = id;
this.buffer = buffer;
} public static void cancel() {
endflag = true;
} public boolean isCanceled() {
return endflag == true;
} /**
* consume:
* 当缓冲区buffer中有字符时,就取出字符显示【相当于消费者】。
*
*/
public Character consume() throws InterruptedException {
return buffer.take(); } /**
* run:
* 一个被创建的任务,只要缓冲区不被置空,就从缓冲区中取出字符消费。
*/
public void run() { while (!isCanceled()) {
try {
log.info(TimeIndicator.getcurrTime() + ":\t" + this + " 取出字符: " + consume());
log.info(TimeIndicator.getcurrTime() + ":\t" + this + " :\t\t\t" + buffer);
} catch (InterruptedException e) {
log.error(this + " Interrupted: " + e.getMessage());
}
}
} public String toString() {
return "C[" + id + "]";
}
}
/**
* TestThread :
*
* 使用主线程不断从键盘缓冲区获取输入,写入自创建的字符缓冲区,并显示缓冲区内容;
* 使用一个子线程不断从自创建的字符缓冲区取出字符输出,并显示缓冲区内容;
*
*/ package zzz.study.threadprogramming.basic.simulation.usequeue; import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*; public class ProducerConsumerProblem { public static void main(String[] args) { int num = 10; System.out.println(" ---- Thread main starts up ---- "); BlockingQueue<Character> queue = new ArrayBlockingQueue<Character>(15);
ExecutorService es = Executors.newCachedThreadPool(); List<ProducerUsingQueue> producers = new ArrayList<ProducerUsingQueue>();
List<ConsumerUsingQueue> comsumers = new ArrayList<ConsumerUsingQueue>(); for (int i=0; i < num; i++) {
producers.add(new ProducerUsingQueue(i, queue));
comsumers.add(new ConsumerUsingQueue(i, queue));
} for (int i=0; i < num; i++) {
es.execute(producers.get(i));
es.execute(comsumers.get(i));
}
es.shutdown();
try {
TimeUnit.SECONDS.sleep(15);
} catch (InterruptedException e) {
e.printStackTrace();
}
ProducerUsingQueue.cancel();
ConsumerUsingQueue.cancel();
es.shutdownNow(); System.out.println("Time to be over."); } }

  这里使用了 log4j 来打印日志,相关配置如下:

    pom.xml 加入以下依赖:

<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>

    log4j.properties 配置:

log4j.rootLogger = INFO,stdout
log4j.logger.appInfo = INFO,appInfoBrefLog log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %p [%c] - [%m]%n log4j.appender.appInfoBrefLog=org.apache.log4j.DailyRollingFileAppender
log4j.appender.appInfoBrefLog.File=./app/info_bref.log
log4j.appender.appInfoBrefLog.DatePattern ='.'yyyy-MM-dd
log4j.appender.appInfoBrefLog.Threshold=INFO
log4j.appender.appInfoBrefLog.Append=true
log4j.appender.appInfoBrefLog.layout=org.apache.log4j.PatternLayout
log4j.appender.appInfoBrefLog.layout.ConversionPattern=%r %m%n

生产者-消费者问题【Java实现】的更多相关文章

  1. 生产者消费者问题Java三种实现

    生产者-消费者Java实现 2017-07-27 1 概述 生产者消费者问题是多线程的一个经典问题,它描述是有一块缓冲区作为仓库,生产者可以将产品放入仓库,消费者则可以从仓库中取走产品. 解决生产者/ ...

  2. 生产者消费者模式-Java实现

    感知阶段 随着软件业的发展,互联网用户的日渐增多,并发这门艺术的兴起似乎是那么合情合理.每日PV十多亿的淘宝,处理并发的手段可谓是业界一流.用户访问淘宝首页的平均等待时间只有区区几秒,但是服务器所处理 ...

  3. 生产者消费者模型Java实现

    生产者消费者模型 生产者消费者模型可以描述为: ①生产者持续生产,直到仓库放满产品,则停止生产进入等待状态:仓库不满后继续生产: ②消费者持续消费,直到仓库空,则停止消费进入等待状态:仓库不空后,继续 ...

  4. 生产者消费者模型java

    马士兵老师的生产者消费者模型,我感觉理解了生产者消费者模型,基本懂了一半多线程. public class ProducerConsumer { public static void main(Str ...

  5. 生产者消费者模型-Java代码实现

    什么是生产者-消费者模式 比如有两个进程A和B,它们共享一个固定大小的缓冲区,A进程产生数据放入缓冲区,B进程从缓冲区中取出数据进行计算,那么这里其实就是一个生产者和消费者的模式,A相当于生产者,B相 ...

  6. 生产者消费者的java实现

    先看最简单的,也就是缓冲区的容量为1 缓冲区容量为1 import java.util.List; public class ProducerAndConsumer2 { static class A ...

  7. JAVA多线程之生产者 消费者模式 妈妈做面包案例

    创建四个类 1.面包类 锅里只可以放10个面包 ---装面包的容器2.厨房 kitchen 生产面包 和消费面包  最多生产100个面包3.生产者4消费者5.测试类 多线程经典案例 import ja ...

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

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

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

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

  10. Java数据结构之队列的实现以及队列的应用之----简单生产者消费者应用

    Java数据结构之---Queue队列 队列(简称作队,Queue)也是一种特殊的线性表,队列的数据元素以及数据元素间的逻辑关系和线性表完全相同,其差别是线性表允许在任意位置插入和删除,而队列只允许在 ...

随机推荐

  1. [LintCode] Move Zeroes 移动零

    Given an array nums, write a function to move all 0's to the end of it while maintaining the relativ ...

  2. java web(三) Tomcat虚拟目录映射方式

    Tomact服务器虚拟目录的映射方式 web应用开发好后若想被外界访问,需要将web应用所在的目录交给web服务器管理,这个过程称为虚拟目录的映射. 方式一:在server.xml文件的host元素中 ...

  3. strcat、strcpy、memcpy 的使用注意

    char *p = "hello";//5 个长度 ; //char *str = (char *)malloc(sizeof(char)*len); ] = "niha ...

  4. android基础知识之一

    1:Android系统架构(重点) 分层的架构 JNI java native interface 1.application :应用层 : java 2.application framework ...

  5. 汇编基础知识之二debug的使用

    DEBUG的使用 (要在win32位习题下进行,win7 64位需要安装DosBox和debug这2个软件): 1:win64位下debug的使用教程: 下载debug.exe,这里我把debug放在 ...

  6. Python 脚本 监控数据库状态

    打算用这个脚本通过zabbix 监控Mariadb的,无奈要等Mariadb完全上线才行,所以先写一个粗略大致功能的版本. #coding:utf-8 #author:shiyiwen #versio ...

  7. iOS9 HTTP传输安全

    1.在Info.plist中添加 NSAppTransportSecurity 类型  Dictionary 2.在 NSAppTransportSecurity 下添加 NSAllowsArbitr ...

  8. python关于列表转为字典的两个小方法

    1.现在有两个列表,list1 = ['key1','key2','key3']和list2 = ['1','2','3'],把他们转为这样的字典:{'key1':'1','key2':'2','ke ...

  9. LFS初次成功+如何粘贴复制LFS命令

    搞了几次LFS,终于成功了,总算舒了一口气. 这次搞LFS用到的资料:LFS文档:http://www.ha97.com/book/lfs-book-6.6/index.html  一份简单的指导文章 ...

  10. 【转】手把手教你把Vim改装成一个IDE编程环境(图文)

    手把手教你把Vim改装成一个IDE编程环境(图文) By: 吴垠 Date: 2007-09-07 Version: 0.5 Email: lazy.fox.wu#gmail.com Homepage ...