生产者消费者是经典的线程之间同步通信问题,生产者线程只有在产品仓库中没有产品的时候才生产产品,当它生成完一个产品以后唤醒消费者线程,消费者线程只有在产品仓库中有产品的时候才能取走产品,然后唤醒生产者线程。
Java可以有好几种方法解决这个问题。首先基础的当然是用Object的wait()、notify()和notifyAll()。
产品仓库类:
//产品仓库 public class ProductStore { private boolean flag = false; public boolean hasProduct(){//是否有产品 return flag; } /** * 生产产品 * @throws Exception */ public synchronized void makeProduct() throws Exception{ while(hasProduct()){//如果生产线程唤醒的还是生产线程,这个被唤醒的生产线程将继续wait this.wait(); } Thread.sleep(300); flag = true; System.out.println(Thread.currentThread().getName()+":生产了一个产品"); this.notifyAll();//唤醒所有线程 } /** * 取走产品 * @throws Exception */ public synchronized void getProduct() throws Exception{ while(!hasProduct()){ this.wait(); } Thread.sleep(100); flag = false; System.out.println(Thread.currentThread().getName()+":取走一个产品"); this.notifyAll(); } }
生产者类:
public class Producer implements Runnable{ ProductStore store; public Producer(ProductStore store){ this.store = store; } @Override public void run() { try { store.makeProduct(); } catch (Exception e) { e.printStackTrace(); } } }
消费者类:
public class Consumer implements Runnable{ ProductStore store; public Consumer(ProductStore store){ this.store = store; } @Override public void run() { try { store.getProduct(); } catch (Exception e) { e.printStackTrace(); } } }
主测试类:
public class Test3 { public static void main(String[] args) { ProductStore store = new ProductStore(); for (int i = 1; i <= 5; i++) { new Thread(new Consumer(store), "消费者"+i).start(); } for (int i = 1; i <= 5; i++) { new Thread(new Producer(store), "生产者"+i).start(); } } }
运行结果:
生产者1:生产了一个产品 消费者4:取走一个产品 生产者4:生产了一个产品 消费者5:取走一个产品 生产者2:生产了一个产品 消费者1:取走一个产品 生产者5:生产了一个产品 消费者2:取走一个产品 生产者3:生产了一个产品 消费者3:取走一个产品
第二种方法就是利用java.util.concurrent包下的Lock得到Conditon,利用Condition的await()、signal()、signalAll()实现线程的通信:
产品仓库类:
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; //产品仓库 public class ProductStore { private boolean flag = false; private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); //得到condition public boolean hasProduct(){//是否有产品 return flag; } /** * 生产产品 * @throws Exception */ public void makeProduct() throws Exception{ lock.lock(); try { while(hasProduct()){//如果生产线程唤醒的还是生产线程,这个被唤醒的生产线程将继续wait condition.await(); } Thread.sleep(300); flag = true; System.out.println(Thread.currentThread().getName()+":生产了一个产品"); condition.signalAll();//唤醒所有线程 } finally{ lock.unlock(); } } /** * 取走产品 * @throws Exception */ public void getProduct() throws Exception{ lock.lock(); try { while(!hasProduct()){ condition.await(); } Thread.sleep(100); flag = false; System.out.println(Thread.currentThread().getName()+":取走一个产品"); condition.signalAll(); } finally { lock.unlock(); } } }
makeProduct和getProduct方法不再使用synchronized修饰,所以使用Lock来控制同步,conditon的await()、singal()、singalAll()分别替换了Object的wait()、notify()和notifyAll()。
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////华丽分割线
好了,发现上面代码中用的都是notifyAll()和singalAll(),如果把notifyAll改成notify,singalAll改成singal会有什么问题呢。因为notify只为在阻塞队列里面随机选一个线程唤醒,那么如果某个消费者拿到一个产品后,notify的任然是一个消费者的线程,那么完了,整个程序就锁住了。看下面的例子:
//产品仓库 public class ProductStore { private boolean flag = false; public boolean hasProduct() {// 是否有产品 return flag; } /** * 生产产品 * * @throws Exception */ public synchronized void makeProduct() throws Exception { while (hasProduct()) {// 如果生产线程唤醒的还是生产线程,这个被唤醒的生产线程将继续wait this.wait(); System.out.println(Thread.currentThread().getName() + "被唤醒了"); } Thread.sleep(300); flag = true; System.out.println(Thread.currentThread().getName() + ":生产了一个产品"); this.notify();// 只唤醒一个线程 } /** * 取走产品 * * @throws Exception */ public synchronized void getProduct() throws Exception { while (!hasProduct()) { this.wait(); System.out.println(Thread.currentThread().getName() + "被唤醒了"); } Thread.sleep(100); flag = false; System.out.println(Thread.currentThread().getName() + ":取走一个产品"); this.notify();// 只唤醒一个线程 } }
执行结果:
生产者1:生产了一个产品 消费者2被唤醒了 消费者2:取走一个产品 消费者4被唤醒了 生产者5:生产了一个产品 消费者1被唤醒了 消费者1:取走一个产品 消费者3被唤醒了
消费者1取完一个产品后,唤醒的是消费者3,所以整个程序死住了。如果用notifyAll的话当然就不会出现这种问题,因为所有的生产者线程被唤醒,并且最终肯定有一个能获得对象锁,从而生产一个产品使得程序顺利执行。
有没有更好的办法解决这个问题呢,如果消费者线程在拿走一个产品后只唤醒生产者线程岂不是更完美,用两个Condition就OK了。代码如下:
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; //产品仓库 public class ProductStore { private boolean flag = false; private Lock lock = new ReentrantLock(); private Condition producerCond = lock.newCondition(); //控制生产者的condition private Condition consumerCond = lock.newCondition(); //控制消费者的condition public boolean hasProduct(){//是否有产品 return flag; } /** * 生产产品 * @throws Exception */ public void makeProduct() throws Exception{ lock.lock(); try { while(hasProduct()){//还有产品,阻塞生产者 producerCond.await(); } Thread.sleep(300); flag = true; System.out.println(Thread.currentThread().getName()+":生产了一个产品"); consumerCond.signal();//唤醒一个消费者 } finally{ lock.unlock(); } } /** * 取走产品 * @throws Exception */ public void getProduct() throws Exception{ lock.lock(); try { while(!hasProduct()){//没有产品,阻塞消费者 consumerCond.await(); } Thread.sleep(100); flag = false; System.out.println(Thread.currentThread().getName()+":取走一个产品"); producerCond.signal();//唤醒一个生产者 } finally { lock.unlock(); } } }
执行结果如下
生产者2:生产了一个产品 消费者1:取走一个产品 生产者4:生产了一个产品 消费者3:取走一个产品 生产者1:生产了一个产品 消费者2:取走一个产品 生产者5:生产了一个产品 消费者5:取走一个产品 生产者3:生产了一个产品 消费者4:取走一个产品
当然,实现生产者消费者还可以用BlockingQueue,见另一篇博客。
相关推荐
生产者消费者模型是一种经典的线程同步问题,它模拟了实际生活中的生产过程和消费过程,使得生产者线程可以将数据生产出来,而消费者线程则负责消耗这些数据,两者之间通过共享数据结构进行协同工作。 生产者消费者...
在并发编程中,"生产者-消费者"模式是一种经典的解决问题的范式,用于协调两个或更多线程间的协作,其中一部分线程(生产者)生成数据,另一部分线程(消费者)消费这些数据。 生产者-消费者模型的核心在于共享资源...
在Java编程中,线程是并发执行的基本单元,它允许程序在同一时间处理多个任务。生产者-消费者问题是多线程编程中的...生产者-消费者问题的解决方式不仅有助于提升程序的并发能力,还能够加深对Java线程同步机制的理解。
使用wait()和notify()实现的生产者与消费者模型,可以了解如何使用wait()和notify()进行线程间通信。(上一次上传的代码有一个问题没有考虑到,这次修补了——CSDN没法撤销资源,只能再上传了)
4. **编写测试类**:创建一个名为 `TestClient.java` 的类来启动生产者和消费者线程,并监控它们的行为。 通过上述步骤,学生不仅可以巩固理论知识,还能实际操作,进一步加深对进程同步与互斥的理解。
【JAVA课程设计(生产者-消费者问题)】是学习多线程编程的一个经典案例,它主要探讨了如何在并发环境中有效地管理和协调生产者线程和消费者线程的交互。在这个设计中,生产者负责生成产品并放入共享资源区,而消费...
总之,生产者消费者模式是多线程编程中的一种重要设计模式,它通过信号量和条件变量实现了线程间的同步和通信,有效提高了系统资源的利用率和整体效率。在Java中,我们可以借助并发库轻松实现这一模式,使得代码更加...
总结来说,"JAVA_生产者-消费者"是一个经典的并发编程模型,它利用Java的线程同步机制和`BlockingQueue`接口来实现高效的数据交换。理解并熟练掌握这一模式对于开发高并发、高性能的Java应用至关重要。通过学习和...
`BlockingQueue`是一种线程安全的数据结构,它实现了生产者和消费者之间的阻塞等待。当队列满时,生产者会阻塞直到有空间可以插入;同样,当队列为空时,消费者也会阻塞等待新的数据。 下面是一个简单的多生产者-多...
本项目通过一个生产者消费者问题的实例,展示了如何在Java中实现线程间的同步与互斥。 生产者消费者问题是经典的并发问题之一,它涉及到两个类型的线程:生产者和消费者。生产者负责生成数据(产品),而消费者则...
在Java中,我们可以使用`java.util.concurrent`包中的工具类来实现生产者-消费者的模型。其中,`BlockingQueue`接口是一个重要的组成部分,它提供了线程安全的队列操作,支持阻塞的插入(put)和删除(take)操作,...
线程池可以配合生产者消费者模式,例如通过提交任务到线程池来实现生产者,线程池中的工作线程充当消费者角色。 在使用生产者消费者模式时,需要注意以下几点: - **线程安全**:确保所有的操作都是线程安全的,...
在Java中实现生产者-消费者模型,主要依赖于线程同步机制,包括`wait()`, `notify()` 和 `notifyAll()` 方法,它们都是在 `Object` 类中定义的。这些方法配合 `synchronized` 关键字使用,可以确保线程安全地访问...
Java线程安全与生产者消费者模型是多线程编程中的两个重要概念,它们在并发处理中扮演着关键角色。在Java中,线程安全是指一个类或者方法在多线程环境下能够正确地处理数据,避免数据的不一致性或竞态条件。而生产者...
4. **阻塞队列(BlockingQueue)**:Java并发包(java.util.concurrent)中的阻塞队列是实现生产者消费者模式的理想选择。它们在内部已经处理了线程同步和等待,提供了一种高效且安全的共享数据方式。例如,`put()`...
### Java多线程-生产者与消费者问题 #### 一、生产者与消费者问题概览 **1.1 概要** 生产者与消费者问题是计算机科学中一个多线程同步的经典问题。它描述了两个线程如何共享有限资源的场景:一个是生产者...
操作系统中的生产者-消费者问题是多线程编程中的经典案例,主要用来展示线程同步和通信的概念。在这个Java实现中,我们将深入理解这个问题的背景、原理以及如何通过Java的并发工具来解决。 生产者-消费者问题的基本...
- **线程通信**:生产者生产完产品后通知消费者,消费者消费完产品后通知生产者。 - **资源管理**:合理使用和管理存储区,确保数据的一致性和完整性。 - **异常处理**:考虑到并发环境下的异常情况,如空指针、并发...
计算机后端-Java-Java核心基础-第20章 多线程 17. 线程通信:生产者消费者例题.avi