生产者-消费者问题【Java实现】
生产者-消费者问题是经典的并发问题, 非常适合并发入门的编程练习。
生产者-消费者问题是指, 有若干个生产者和若干个消费者并发地读写一个或多个共享存储空间;生产者创建对象并放入到共享存储空间,消费者从共享存储空间取出对象进行消费处理。当共享存储空间为满时,生产者被阻塞;当共享存储空间为空时,消费者被阻塞。本文先使用一个自定义的有限长度字符序列缓冲区来作为共享存储空间,并使用原生的 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实现】的更多相关文章
- 生产者消费者问题Java三种实现
生产者-消费者Java实现 2017-07-27 1 概述 生产者消费者问题是多线程的一个经典问题,它描述是有一块缓冲区作为仓库,生产者可以将产品放入仓库,消费者则可以从仓库中取走产品. 解决生产者/ ...
- 生产者消费者模式-Java实现
感知阶段 随着软件业的发展,互联网用户的日渐增多,并发这门艺术的兴起似乎是那么合情合理.每日PV十多亿的淘宝,处理并发的手段可谓是业界一流.用户访问淘宝首页的平均等待时间只有区区几秒,但是服务器所处理 ...
- 生产者消费者模型Java实现
生产者消费者模型 生产者消费者模型可以描述为: ①生产者持续生产,直到仓库放满产品,则停止生产进入等待状态:仓库不满后继续生产: ②消费者持续消费,直到仓库空,则停止消费进入等待状态:仓库不空后,继续 ...
- 生产者消费者模型java
马士兵老师的生产者消费者模型,我感觉理解了生产者消费者模型,基本懂了一半多线程. public class ProducerConsumer { public static void main(Str ...
- 生产者消费者模型-Java代码实现
什么是生产者-消费者模式 比如有两个进程A和B,它们共享一个固定大小的缓冲区,A进程产生数据放入缓冲区,B进程从缓冲区中取出数据进行计算,那么这里其实就是一个生产者和消费者的模式,A相当于生产者,B相 ...
- 生产者消费者的java实现
先看最简单的,也就是缓冲区的容量为1 缓冲区容量为1 import java.util.List; public class ProducerAndConsumer2 { static class A ...
- JAVA多线程之生产者 消费者模式 妈妈做面包案例
创建四个类 1.面包类 锅里只可以放10个面包 ---装面包的容器2.厨房 kitchen 生产面包 和消费面包 最多生产100个面包3.生产者4消费者5.测试类 多线程经典案例 import ja ...
- 第23章 java线程通信——生产者/消费者模型案例
第23章 java线程通信--生产者/消费者模型案例 1.案例: package com.rocco; /** * 生产者消费者问题,涉及到几个类 * 第一,这个问题本身就是一个类,即主类 * 第二, ...
- Java里的生产者-消费者模型(Producer and Consumer Pattern in Java)
生产者-消费者模型是多线程问题里面的经典问题,也是面试的常见问题.有如下几个常见的实现方法: 1. wait()/notify() 2. lock & condition 3. Blockin ...
- Java数据结构之队列的实现以及队列的应用之----简单生产者消费者应用
Java数据结构之---Queue队列 队列(简称作队,Queue)也是一种特殊的线性表,队列的数据元素以及数据元素间的逻辑关系和线性表完全相同,其差别是线性表允许在任意位置插入和删除,而队列只允许在 ...
随机推荐
- java实现MD5加密
mport java.security.MessageDigest; import java.security.NoSuchAlgorithmException; public class Creat ...
- 2016HUAS暑假集训训练题 G - Oil Deposits
Description The GeoSurvComp geologic survey company is responsible for detecting underground oil dep ...
- select的5中子句where,group by, havaing, order by, limit的使用顺序及实例
-- 语法: SELECT select_list FROM table_name [ WHERE search_condition ] [ GROUP BY group_by_expression ...
- js 字符串拼接
正常来说已经使用es6 的 模板了如`` //页面层 layer.open({ type: 1, content:`<div class="child_card"> & ...
- BizTalk开发系列(十) ESB Guidance安装笔记
ESB指导工具包(ESB Guidance)是一个运行于BizTalk Server 2006 R2之上的一个框架.详细信息访问ESB指导工具包社区网站 .源码下载 ESB Guidance的安装过程 ...
- jquery选择伪元素属性的方法
CSS伪元素不是DOM元素,因此你无法直接选择到它们 一个方法是为该元素添加新类,并通过设置新类的属性来达到改变伪元素属性的效果: .checkboxWrapper.selected::before{ ...
- CentOS7配置日志(VirtualBox)
版本为CentOS-Minimal 1.VirtualBox下安装CentOS. 新建虚拟机 下载CentOS,放入盘片,启动虚拟机,按提示开始安装(建议内存1G,硬盘10G以上) 2. 设置网络 ...
- ios计算内容的高度 (含7.0前及以后的版本的用法)
+ (CGFloat)heightForContent:(MyMsgTextModel *)content withWidth:(CGFloat)width { CGSize contentSize; ...
- WordPress基础:极简安装教程
1.下载WordPress 2.将解压后的文件夹,放到网站根目录,并重命名为你喜欢的目录如:w, 3.重命名文件wp-config-sample.php 为 wp-config.php,并进行配置 4 ...
- Web性能API——帮你分析Web前端性能
前端性能统计必备api,有不知道的吗? 正文从这开始- 开发一个现代化的互联网网站是一项复杂的任务,需要各种职能的密切合作以应对用户日新月异的需求.其中,网页的性能直接决定了用户的体验,而随着新型客户 ...