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

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

    LaTex中字体大小有很多中等级,分别由下列命令控制: \tiny \scriptsize \footnotesize \small \normalsize \large \Large \LARGE ...

  2. Hadoop_HDFS HA 及解决方案

    1. HDFS系统架构 HDFS(Hadoop Distributed File System),及Hadoop分布式文件系统 作用: 为Hadoop分布式计算框架提供高性能,高可靠,高可扩展的存储服 ...

  3. SQL Server 中字符串中包含字符串变量的表示方法

    在代码中有如下的需求:需要在数据库中使用 in 关键字做删除的时候,又需要使用到参数化,参数又是字符串,所以使用的时候就按照如下方式 StringBuilder sql = new StringBui ...

  4. php连接多数据库

    <?php $conn1=mysql_connect('localhost','root','','new_link '); $conn2=mysql_connect('localhost',' ...

  5. BizTalk开发系列(十四) XML空白字符(WhiteSpace)

    最近在做一个BizTalk项目,对XML文件的处理很复杂.本来是想找有没有方法可以一次性去除XML文件中节点和属性的值的空格.但是找了很久没有看到相关的方法.如果有知道该方法的麻烦跟我讲一下:cbcy ...

  6. 面向系统管理员的10款Linux GUI工具 (转自51cto)

    如果你是名系统管理员,现已到了Linux非知道不可的地步.如果你在更庞大的环境下工作,更是如此.许多企业组织已迁离了一切都借助点击式GUI来管理的Windows.幸好,Linux也有许多GUI工具可以 ...

  7. P1074 靶形数独 - 40

    #include <bits/stdc++.h> #define searchnext(x, y) y == 9 ? search(x + 1, 1) : search(x, y + 1) ...

  8. Vim ide for shell development

    Source : This article is part of the ongoing Vi / Vim Tips and Tricks Series. As a Linux sysadmin or ...

  9. ArrayBlockingQueue跟LinkedBlockingQueue的区别

    .队列中的锁的实现不同 ArrayBlockingQueue中的锁是没有分离的,即生产和消费用的是同一个锁: LinkedBlockingQueue中的锁是分离的,即生产用的是putLock,消费是t ...

  10. 在windows下新建maven项目

    1.拷贝settings到.m2文件下 2.修改文件 3.新建Project项目 4.转换为maven项目 config下转换 5.拷贝pom文件 6.新建目录 src/main/java src/m ...