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

   生产者-消费者问题是指, 有若干个生产者和若干个消费者并发地读写一个或多个共享存储空间;生产者创建对象并放入到共享存储空间,消费者从共享存储空间取出对象进行消费处理。当共享存储空间为满时,生产者被阻塞;当共享存储空间为空时,消费者被阻塞。本文先使用一个自定义的有限长度字符序列缓冲区来作为共享存储空间,并使用原生的 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. ado.net 连接,删除,添加

    ado.net数据库访问技术将数据库中的数据,提取到内存中,展示给用户看还可以将内存中的数据写入数据库中去 并不是唯一的数据库访问技术,但是它是最底层的数据库访问技术 数据库: create data ...

  2. Java 获取当前系统时间方法比较

    转载: http://blog.csdn.net/zzjjiandan/article/details/8372617 一. 获取当前系统时间和日期并格式化输出: import java.util.D ...

  3. 为什么使用ConcurrentHashMap

    ConcurrentHashMap是有Segment数组结构和HashEntry数组结构组成. Segment是一种可重入锁(ReentrantLock),在ConcurrentHashMap里扮演锁 ...

  4. java类加载器的层次结构

    类加载器的层次结构: 引导类加载器(bootstrap class loader) 用来加载java的核心库(JAVA_HOME/jre/lib/rt.jar,或sun.boot.class.path ...

  5. smartlink

    链接:https://www.zhihu.com/question/21783165/answer/20323202 Wi-Fi本身是属于固定频段上的TDD通讯机制,目前尚用的也就是2.4G和5.8G ...

  6. Java碎片知识(笔记)

    1.在java中有goto,但这只是保留字,并不能使用(const也是).在eclipse中的报错信息为”Syntax error on token "goto", throw e ...

  7. angularJs模糊查询

    html代码 <!DOCTYPE html><html> <head> <meta charset="UTF-8"> <tit ...

  8. A trip through the Graphics Pipeline 2011_05

    After the last post about texture samplers, we’re now back in the 3D frontend. We’re done with verte ...

  9. 找规律 ZOJ3498 Javabeans

    Javabeans are delicious. Javaman likes to eat javabeans very much. Javaman has n boxes of javabeans. ...

  10. Android 网络通信框架Volley简介

    1.1. Volley引入的背景在以前,我们可能面临如下很多麻烦的问题. 比如以前从网上下载图片的步骤可能是这样的流程: 在ListAdapter#getView()里开始图像的读取. 通过Async ...