在生产者和消费者的实例中,如何实现线程并发和共享资源?

作者&投稿:仪狮 (若有异议请与网页底部的电邮联系)
如何实现消费者与生产者的多线程编程~

package StackDemo;

import java.util.Random;
import java.util.Stack;

public class StackDemo {

private static Stack stack = new Stack();

class Worker implements Runnable {
private String name;
private Random random = new Random();
private int data;
private boolean isProducer;

public Worker(String name, boolean isProducer) {
this.name = name;
this.isProducer = isProducer;
}

public void run() {
while (true) {
synchronized (this) {
if (isProducer) {
if (StackDemo.stack.size() < 200) {

try {
Thread.sleep(600);
} catch (InterruptedException e) {
e.printStackTrace();
}

data = random.nextInt(1000);
StackDemo.stack.push(data);
System.out.println(Thread.currentThread().getName()
+ this.name + "生产了" + data + " 堆栈里还剩下"
+ StackDemo.stack.size() + "个数据");

}
} else {
if (!StackDemo.stack.isEmpty()) {

try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
int data = StackDemo.stack.pop();
System.out.println(Thread.currentThread().getName()
+ this.name + "消耗了" + data + " 堆栈里还剩下"
+ StackDemo.stack.size() + "个数据");

}
}
}
}
}

}

public static void main(String[] args) {
StackDemo stackDemo = new StackDemo();
Worker p1 = stackDemo.new Worker("生产者", true);
Worker t1 = stackDemo.new Worker("消费者", false);
new Thread(p1).start();
new Thread(p1).start();
new Thread(p1).start();
new Thread(t1).start();
new Thread(t1).start();
}

}

制片人:时存在的产品的时候,
消费者生产的产品是不够的:消费类产品
关键:当产品到达上限,停止生产,通知消费者的消费
关键2:当达到下限,停止消费,并通知生产者生产
共享数据:

代码
进口的java.util.ArrayList;
BR />公共类ProducerAndConsumer {
ArrayList的产品=新的ArrayList ();
MAX = 10;
布尔取消;

同步失效产生(){
int的大小= products.size();
(大小<MAX){
products.add(大小+“”);
} {
notifyAll()的;
}
}

同步的无效消耗(){
(products.isEmpty()){
尝试{
的wait();
}(InterruptedException的E){
e.printStackTrace();
}
}
System.out.println( “消费products.remove:”+(0));
}

私有类生产扩展Thread {
@覆盖
公共无效的run(){...... />(取消){
农产品();
}
}
}

私人阶层的消费扩展Thread {
@覆盖
公共无效的run(){
(取消){
消耗();
}
}
}
/>无效取消(){
取消= TRUE;
}

的公共ProducerAndConsumer(){
新的消费者()。开始();
新的的监制()。开始();}


公共静态无效的主要(字符串[] ARGS){
新ProducerAndConsumer();
}

}

解决思路

在现实应用中,很多时候都需要让多个线程按照一定的次序来访问共享资源,例如,经典的生产者和消费者问题。这类问题描述了这样一种情况,假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中的产品取走消费。如果仓库中没有产品,则生产者可以将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止。如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止。显然,这是一个同步问题,生产者和消费者共享同一资源,并且,生产者和消费者之间彼此依赖,互为条件向前推进。但是,该如何编写程序来解决这个问题呢?

传统的思路是利用循环检测的方式来实现,这种方式通过重复检查某一个特定条件是否成立来决定线程的推进顺序。比如,一旦生产者生产结束,它就继续利用循环检测来判断仓库中的产品是否被消费者消费,而消费者也是在消费结束后就会立即使用循环检测的方式来判断仓库中是否又放进产品。显然,这些操作是很耗费CPU资源的,不值得提倡。那么有没有更好的方法来解决这类问题呢?

首先,当线程在继续执行前需要等待一个条件方可继续执行时,仅有 synchronized 关键字是不够的。因为虽然synchronized关键字可以阻止并发更新同一个共享资源,实现了同步,但是它不能用来实现线程间的消息传递,也就是所谓的通信。而在处理此类问题的时候又必须遵循一种原则,即:对于生产者,在生产者没有生产之前,要通知消费者等待;在生产者生产之后,马上又通知消费者消费;对于消费者,在消费者消费之后,要通知生产者已经消费结束,需要继续生产新的产品以供消费。

其实,Java提供了3个非常重要的方法来巧妙地解决线程间的通信问题。这3个方法分别是:wait()、notify()和notifyAll()。它们都是Object类的最终方法,因此每一个类都默认拥有它们。

虽然所有的类都默认拥有这3个方法,但是只有在synchronized关键字作用的范围内,并且是同一个同步问题中搭配使用这3个方法时才有实际的意义。

这些方法在Object类中声明的语法格式如下所示:

final void wait() throws InterruptedException

final void notify()

final void notifyAll()

其中,调用wait()方法可以使调用该方法的线程释放共享资源的锁,然后从运行态退出,进入等待队列,直到被再次唤醒。而调用notify()方法可以唤醒等待队列中第一个等待同一共享资源的线程,并使该线程退出等待队列,进入可运行态。调用notifyAll()方法可以使所有正在等待队列中等待同一共享资源的线程从等待状态退出,进入可运行状态,此时,优先级最高的那个线程最先执行。显然,利用这些方法就不必再循环检测共享资源的状态,而是在需要的时候直接唤醒等待队列中的线程就可以了。这样不但节省了宝贵的CPU资源,也提高了程序的效率。

由于wait()方法在声明的时候被声明为抛出InterruptedException异常,因此,在调用wait()方法时,需要将它放入try…catch代码块中。此外,使用该方法时还需要把它放到一个同步代码段中,否则会出现如下异常:

"java.lang.IllegalMonitorStateException: current thread not owner"

这些方法是不是就可以实现线程间的通信了呢?下面将通过多线程同步的模型: 生产者和消费者问题来说明怎样通过程序解决多线程间的通信问题。

具体步骤

下面这个程序演示了多个线程之间进行通信的具体实现过程。程序中用到了4个类,其中ShareData类用来定义共享数据和同步方法。在同步方法中调用了wait()方法和notify()方法,并通过一个信号量来实现线程间的消息传递。

// 例4.6.1 CommunicationDemo.java 描述:生产者和消费者之间的消息传递过程

class ShareData

{
private char c;
private boolean isProduced = false; // 信号量
public synchronized void putShareChar(char c) // 同步方法putShareChar()
{
if (isProduced) // 如果产品还未消费,则生产者等待
{
try
{
wait(); // 生产者等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.c = c;
isProduced = true; // 标记已经生产
notify(); // 通知消费者已经生产,可以消费
}

public synchronized char getShareChar() // 同步方法getShareChar()
{
if (!isProduced) // 如果产品还未生产,则消费者等待
{
try
{
wait(); // 消费者等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
isProduced = false; // 标记已经消费
notify(); // 通知需要生产
return this.c;
}
}

class Producer extends Thread // 生产者线程
{
private ShareData s;

Producer(ShareData s)
{
this.s = s;
}

public void run()
{
for (char ch = 'A'; ch <= 'D'; ch++)
{
try
{
Thread.sleep((int) (Math.random() * 3000));
} catch (InterruptedException e) {
e.printStackTrace();
}
s.putShareChar(ch); // 将产品放入仓库
System.out.println(ch + " is produced by Producer.");
}
}
}

class Consumer extends Thread // 消费者线程
{
private ShareData s;

Consumer(ShareData s)
{
this.s = s;
}

public void run()
{
char ch;
do {
try
{
Thread.sleep((int) (Math.random() * 3000));
} catch (InterruptedException e) {
e.printStackTrace();
}
ch = s.getShareChar(); // 从仓库中取出产品
System.out.println(ch + " is consumed by Consumer. ");
} while (ch != 'D');
}
}

class CommunicationDemo
{
public static void main(String[] args)
{
ShareData s = new ShareData();
new Consumer(s).start();
new Producer(s).start();
}
}

上面的程序演示了生产者生产出A、B、C、D四个字符,消费者消费这四个字符的全过程,程序结果如图4.6.1所示:

图4.6.1 生产者和消费者举例

通过程序的运行结果可以看到,尽管在主方法中先启动了Consumer线程,但是,由于仓库中没有产品,因此,Consumer线程就会调用 wait()方法进入等待队列进行等待,直到Producer线程将产品生产出来并放进仓库,然后使用notify()方法将其唤醒。

由于在两个线程中都指定了一定的休眠时间,因此也可能出现这样的情况:生产者将产品生产出来放入仓库,并通知等待队列中的Consumer线程,然而,由于休眠时间过长,Consumer线程还没有打算消费产品,此时,Producer线程欲生产下一个产品,结果由于仓库中的产品没有被消费掉,故 Producer线程执行wait()方法进入等待队列等待,直到Consumer线程将仓库中的产品消费掉以后通过notify()方法去唤醒等待队列中的Producer线程为止。可见,两个线程之间除了必须保持同步之外,还要通过相互通信才能继续向前推进。

前面这个程序中,生产者一次只能生产一个产品,而消费者也只能一次消费一个产品。那么现实中也有这样的情况,生产者可以一次生产多个产品,只要仓库容量够大,就可以一直生产。而消费者也可以一次消费多个产品,直到仓库中没有产品为止。

但是,无论是生产产品到仓库,还是从仓库中消费,每一次都只能允许一个操作。显然,这也是个同步问题,只不过在这个问题中共享资源是一个资源池,可以存放多个资源。下面就以栈结构为例给出如何在这个问题中解决线程通信的程序代码。

// 例4.6.2 CommunicationDemo2.java

class SyncStack // 同步堆栈类,可以一次放入多个数据
{
private int index = 0; // 堆栈指针初始值为0
private char[] buffer = new char[5]; // 堆栈有5个字符的空间
public synchronized void push(char c) // 入栈同步方法
{
if (index == buffer.length) // 堆栈已满,不能入栈
{
try
{
this.wait(); // 等待出栈线程将数据出栈
} catch (InterruptedException e) {
}
}
buffer[index] = c; // 数据入栈
index++; // 指针加1,栈内空间减少
this.notify(); // 通知其他线程把数据出栈
}

public synchronized char pop() // 出栈同步方法
{
if (index == 0) // 堆栈无数据,不能出栈
{
try
{
this.wait(); // 等待入栈线程把数据入栈
} catch (InterruptedException e) {
}
}
this.notify(); // 通知其他线程入栈
index--; // 指针向下移动
return buffer[index]; // 数据出栈
}
}

class Producer implements Runnable // 生产者类
{
SyncStack s; // 生产者类生成的字母都保存到同步堆栈中
public Producer(SyncStack s)
{
this.s = s;
}

public void run()
{
char ch;
for (int i = 0; i < 5; i++)
{
try
{
Thread.sleep((int) (Math.random() * 1000));
} catch (InterruptedException e) {
}
ch = (char) (Math.random() * 26 + 'A'); // 随机产生5个字符
s.push(ch); // 把字符入栈
System.out.println("Push " + ch + " in Stack"); // 打印字符入栈
}
}
}

class Consumer implements Runnable // 消费者类
{
SyncStack s; // 消费者类获得的字符都来自同步堆栈
public Consumer(SyncStack s)
{
this.s = s;
}

public void run()
{
char ch;
for (int i = 0; i < 5; i++)
{
try
{
Thread.sleep((int) (Math.random() * 3000));
} catch (InterruptedException e) {
}
ch = s.pop(); // 从堆栈中读取字符
System.out.println("Pop " + ch + " from Stack"); // 打印字符出栈
}
}
}

public class CommunicationDemo2
{
public static void main(String[] args)
{
SyncStack stack = new SyncStack();
// 下面的消费者类对象和生产者类对象所操作的是同一个同步堆栈对象
Thread t1 = new Thread(new Producer(stack)); // 线程实例化
Thread t2 = new Thread(new Consumer(stack)); // 线程实例化
t2.start(); // 线程启动
t1.start(); // 线程启动
}
}

程序中引入了一个堆栈数组buffer[]来模拟资源池,并使生产者类和消费者类都实现了Runnable接口,然后在主程序中通过前面介绍的方法创建两个共享同一堆栈资源的线程,并且有意先启动消费者线程,后启动生产者线程。请在阅读程序的时候仔细观察例4.6.1和本例的相似点以及区别之处,体会作者的用心。程序结果输出如图4.6.2所示:

图4.6.2 共享资源池的生产者和消费者问题

由于是栈结构,所以符合后进先出原则。有兴趣的读者还可以用符合先进先出原则的队列结构来模拟线程间通信的过程,相信可以通过查阅相关的资料来解决这个问题,在这里就不再给出程序代码了,作为一个思考题供读者练习。

专家说明

本小节介绍了三个重要的方法:wait()、notify()和notifyAll()。使用它们可以高效率地完成多个线程间的通信问题,这样在通信问题上就不必再使用循环检测的方法来等待某个条件的发生,因为这种方法是极为浪费CPU资源的,当然这种情况也不是所期望的。在例4.6.1中,为了更好地通信,引入了一个专门用来传递信息的信号量。利用信号量来决定线程是否等待无疑是一种非常安全的操作,值得提倡。此外,在例4.6.2中引入了资源池作为共享资源,并解决了在这种情况下如何实现多线程之间的通信问题。希望读者能够举一反三,编写出解决更加复杂问题的程序。

专家指点

可以肯定的是,合理地使用wait()、notify()和notifyAll()方法确实能够很好地解决线程间通信的问题。但是,也应该了解到这些方法是更复杂的锁定、排队和并发性代码的构件。尤其是使用 notify()来代替notifyAll()时是有风险的。除非确实知道每一个线程正在做什么,否则最好使用notifyAll()。其实,在 JDK1.5中已经引入了一个新的包:java.util.concurrent 包,该包是一个被广泛使用的开放源码工具箱,里面都是有用的并发性实用程序。完全可以代替wait()和notify()方法用来编写自己的调度程序和锁。有关信息可以查阅相关资料,本书中不再赘述。

相关问题

Java提供了各种各样的输入输出流(stream),使程序员能够很方便地对数据进行操作。其中,管道(pipe)流是一种特殊的流,用于在不同线程间直接传送数据。一个线程发送数据到输出管道,另一个线程从输入管道中读出数据。通过使用管道,达到实现多个线程间通信的目的。那么,如何创建和使用管道呢?

Java提供了两个特殊的专门用来处理管道的类,它们就是PipedInputStream类和PipedOutputStream类。

其中,PipedInputStream代表了数据在管道中的输出端,也就是线程从管道读出数据的一端;PipedOutputStream代表了数据在管道中的输入端,也就是线程向管道写入数据的一端,这两个类一起使用就可以创建出数据输入输出的管道流对象。

生产者:当产品不够时生产产品
消费者:当产品存在时消费产品
临界一:当产品达到上限时,停止生产,通知消费者消费
临界二:当产品达到下限时,停止消费,通知生产者生产
共享数据:产品

code
import java.util.ArrayList;

public class ProducerAndConsumer {
ArrayList<String> products = new ArrayList<String>();
int MAX = 10;
boolean cancel;

synchronized void produce() {
int size = products.size();
if (size < MAX) {
products.add(size + "");
} else {
notifyAll();
}
}

synchronized void consume() {
if (products.isEmpty()) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("consume:" + products.remove(0));
}

private class Producer extends Thread {
@Override
public void run() {
while (!cancel) {
produce();
}
}
}

private class Consumer extends Thread {
@Override
public void run() {
while (!cancel) {
consume();
}
}
}

void cancel() {
cancel = true;
}

public ProducerAndConsumer() {
new Consumer().start();
new Producer().start();
}

public static void main(String[] args) {
new ProducerAndConsumer();
}

}


消费者市场与生产者市场有什么样的区别
消费者市场与生产者市场(又叫做产业市场)有3点不同:一、两者的实质不同:1、消费者市场的实质:指为满足自身需要而购买的一切个人和家庭构成的市场。2、生产者市场的实质:是由那些购买货物和劳务,并用来生产其它货物和劳务,以出售、出租给其他人的个人或组织构成。二、两者的构成不同:1、消费者...

消费者市场与生产者市场有什么样的区别
1. 实质不同:消费者市场的实质是指为满足个人和家庭需求而购买商品和服务的市场,而生产者市场的实质是由购买货物和劳务用于生产其他货物和劳务,然后出售或出租给其他个人或组织的市场。2. 构成不同:消费者市场主要包括生产者市场、中间商市场和政府市场,而生产者市场主要由农业、林业、渔业、采矿业、...

消费者市场与生产者市场有什么样的区别
主要区别是,性质不同、特点不同、作用不同,具体如下:一、性质不同1、生产者市场产业市场又叫生产者市场或工业市场,是由购买货物和劳务,并用来生产其它货物和劳务,以出售、出租给其他人的个人或组织构成。2、消费者市场消费者市场是指为满足自身需要而购买的一切个人和家庭构成的市场。

生产者均衡与消费者均衡的现实意义?
消费者均衡:它是研究单个消费者在既定收入条件下实现效用最大化的均衡条件。生产者均衡:当某生产者在给定的成本约束下,产出量达到最大时, 生产者达到最优的经营状态,生产要素组合达到最佳 。指导消费者和生产者用有限的资源,达到各自经济主体收益最大化的一种状态 这两个理论模型用数学方法解释了理...

在生产者和消费者的实例中,如何实现线程并发和共享资源?
在现实应用中,很多时候都需要让多个线程按照一定的次序来访问共享资源,例如,经典的生产者和消费者问题。这类问题描述了这样一种情况,假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中的产品取走消费。如果仓库中没有产品,则生产者可以将产品放入仓库,否则停止生产并等待,直到仓库中的产品被...

生产者与消费者之间的关系侵权
实践中,产品使用者(消费者、受害人、赔偿权利人)在销售者出售产品之前对产品的生产和物流过程并不清楚,当然无从证明缺陷的导致者,因此,第四十三条更多的是基于从程序上对赔偿权利人的保障,而不是从归责原则来立法,是对第四十二条和第四十一条的合理的有机补充。 另外,有一种观点认为,不应...

什么是计划市场经济体制?核心理论是什么?
一、计划-市场经济体制的核心理论 《初论》的研究发现:在生产、消费、交换环节中,社会控制力的着力点不同,构建的经济体制也不同。社会控制力着力点在生产环节上时,主要通过政府管控生产者的计划来控制经济,构建了计划经济体制;这种经济体制更适合生产者生产资料公有制。社会控制力的着力点在消费环节...

电子中间商与传统中间商的异同
1)存在前提不同。传统中间商是因为生产者和消费者直接达成交易成本较高;而电子中间商是对传统直销的替代,是中间商职能和功效在新的领域的发展和延伸。2)交易主体不同。传统中间商是要直接参加生产者和消费者交易活动的,而且是交易的轴心和驱动力;而电子中间商作为一个独立主体存在,它不直接参与...

为什么说商品的生产者与消费者不能同时拥有商品的使用价值与价值?
商品生产者希望“物美价丰”,消费者希望“物美价廉”都是将商品作为使用价值与价值的统一体来看待和考察的。当然商品的使用价值与价值的最终实现,是以两者的分离为条件。商品的生产是获得商品的价值,为此必须将商品的使用价值让渡给消费者。消费者是为获得商品的使用价值,为此必须让渡商品的价值给生产者...

<生产消费者力量>生产消费者和消费者的区别
消费者科学上的定义为,为食物链的中的一个环节,代表着不能生产,只能通过消耗其他生物来达到自我存活的生物。生产消费者和消费者的区别,在于生产消费者可以理解为推销员或直销员,能够更好发挥生产消费者在推动国民经济发展中的力量。生产者赚钱,消费者花钱,生产消费者在花钱的同时赚钱。生产消费者是...

嘉祥县15097714788: 如何实现生产者消费者模式 -
梁非复方: 使用的生产者和消费者模型具有如下特点: (1)本实验的多个缓冲区不是环形循环的,也不要求按顺序访问.生产者可以把产品放到目前某一个空缓冲区中. (2)消费者只消费指定生产者的产品. (3)在测试用例文件中指定了所...

嘉祥县15097714788: 编程题:用c++实现生产者和消费者问题 -
梁非复方: 实现一个队列CQueue CQueue提供两个公有成员函数 addTail():往队列尾部增加一个元素 removeHead():读出并移除队列的第一个元素 生产者:两个线程通过调用CQueue::addTail()往队列中增加元素 消费者:一个线程通过调用...

嘉祥县15097714788: 如何用C语言实现多线程下生产者消费者互斥同步问题 -
梁非复方: 整个程序以伪代码形式给出,当做一个提示吧,这样你就应该有思路了 生产者在存储区满时不能再生产,进入等待,消费者同理 完成同步互斥效果/*----以下为代码部分-----*///定义全局变量 int empty = 1;//信号量表示存储单元空,可以生产产品 ...

嘉祥县15097714788: 用C语言实现 -- 生产者与消费者的问题(PV操作) -
梁非复方: 这个问题蛮好的 我给你个程序 给我加分啊#include <windows.h>#include <stdio.h>#include <stdlib.h>typedef HANDLE Semaphore; // 信号量的Windows原型#define P(S) WaitForSingleObject(S, INFINITE) // 定义Windows下的P操作#...

嘉祥县15097714788: 请问如何用C语言实现“生产者与消费者问题”?(最好附上完整的C语言源代码) -
梁非复方: 原理很简单,但是实现是非常复杂的. 其中牵涉到大量的用于系统内核的C语言库函数,对于刚刚开始学习的学生来说,那实在是太困难了. 我也学操作系统.P语言描述的也不错,就凑合着用吧. 这里有完整源代码38K.你可以想象有多么夸张了.见我的参考资料. 参考资料: http://www.hsip.cn/soft/3/2006/1D1513422145.html

嘉祥县15097714788: 如何实现商品的价值? -
梁非复方: 商品价值量是指商品价值的大小,要衡量价值的决定,就是通过价值量.商品的价值是通过交换来实现的.也就是说对于一件商品要实现他的价值就应该把它销售出去,卖给顾客. 商品的价值由劳动创造,商品价值量的决定,首先是通过生产部门内部商品生产者之间的竞争,使个别劳动时间转化为社会必要劳动时间,个别价值转化为社会价值即市场价值的过程.

嘉祥县15097714788: 如何用c++builder 编写多线程 -
梁非复方: 摘 要:本文简单介绍了Windows环境下进行多线程编程的意义,重点讨论了C++Builder环境下开发多线程应用程序这一问题,并通过实现生产者-消费者问题,帮我们更好地理解同步概念及其实现方法. 关键词:多线程;同步;生产者-消费者;...

嘉祥县15097714788: 用C语言实现PV操作生产者消费者关系 -
梁非复方: #include <windows.h>#include <stdio.h>#include <stdlib.h> typedef HANDLE Semaphore; // 信号量的Windows原型#define P(S) WaitForSingleObject(S, INFINITE) // 定义Windows下的P操作#define V(S) ReleaseSemaphore(S, 1, NULL) // ...

嘉祥县15097714788: 生产者消费者算法模拟,用C语言实现,我财富值只有这么多了,求高手帮忙,... -
梁非复方: #include#include void main() { int full=0; //用于判断缓冲池是否为满 int emputy=15; //用于判断缓冲池时候为空 char buffer[15][10]; //用于存放产品 char ch[10]; //用于接收生产的产品和消费的产品 int i=0,j=0; int num; int number; int numb; printf...

嘉祥县15097714788: 怎样用C++实现生产者消费者的模拟 -
梁非复方: // producer_consumer.cpp//////////////////////////////////////////////////////////////////////// 有一个生产者在生产产品,这些产品将提供给若干个消费者去消费,为了使生产者和消费者能并发执行,// 在两者之间设置一个有多个缓冲区的缓冲池,生产者将它生产的产品...

本站内容来自于网友发表,不代表本站立场,仅表示其个人看法,不对其真实性、正确性、有效性作任何的担保
相关事宜请发邮件给我们
© 星空见康网