在Java中使用线程的生产者-消费者解决方案
在计算中,生产者-消费者问题(也称为有界缓冲区问题)是多进程同步问题的经典示例。该问题描述了两个进程,生产者和消费者,它们共享一个公共的、固定大小的缓冲区,用作队列。
- 生产者的工作是生成数据,放入缓冲区,然后重新开始。
- 同时,消费者一次一块地消费数据(即从缓冲区中删除)。
问题
确保生产者在缓冲区已满时不会尝试将数据添加到缓冲区中,并且消费者不会尝试从空缓冲区中删除数据。
解决方案
如果缓冲区已满,生产者要么进入睡眠状态,要么丢弃数据。下次消费者从缓冲区中删除一个项目时,它会通知生产者,生产者再次开始填充缓冲区。同样,如果发现缓冲区为空,消费者可以进入睡眠状态。下次生产者将数据放入缓冲区时,它会唤醒睡眠中的消费者。
不恰当的解决方案可能会导致两个进程都在等待唤醒的死锁。
推荐阅读——JAVA中的多线程、 Java中的同步、Java间通信
生产者消费者类的实现
- 一个LinkedList 列表——存储队列中的作业列表。
- 可变容量- 检查列表是否已满
- 一种控制从该列表中插入和提取的机制,以便我们在列表已满时不插入到列表中,如果列表为空则从列表中移除。
注意:建议在离线 IDE 上测试以下程序,因为无限循环和 sleep 方法可能会导致它在任何在线 IDE 上超时
Java
// Java program to implement solution of producer
// consumer problem.
import java.util.LinkedList;
public class Threadexample {
public static void main(String[] args)
throws InterruptedException
{
// Object of a class that has both produce()
// and consume() methods
final PC pc = new PC();
// Create producer thread
Thread t1 = new Thread(new Runnable() {
@Override
public void run()
{
try {
pc.produce();
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
});
// Create consumer thread
Thread t2 = new Thread(new Runnable() {
@Override
public void run()
{
try {
pc.consume();
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
});
// Start both threads
t1.start();
t2.start();
// t1 finishes before t2
t1.join();
t2.join();
}
// This class has a list, producer (adds items to list
// and consumer (removes items).
public static class PC {
// Create a list shared by producer and consumer
// Size of list is 2.
LinkedList list = new LinkedList<>();
int capacity = 2;
// Function called by producer thread
public void produce() throws InterruptedException
{
int value = 0;
while (true) {
synchronized (this)
{
// producer thread waits while list
// is full
while (list.size() == capacity)
wait();
System.out.println("Producer produced-"
+ value);
// to insert the jobs in the list
list.add(value++);
// notifies the consumer thread that
// now it can start consuming
notify();
// makes the working of program easier
// to understand
Thread.sleep(1000);
}
}
}
// Function called by consumer thread
public void consume() throws InterruptedException
{
while (true) {
synchronized (this)
{
// consumer thread waits while list
// is empty
while (list.size() == 0)
wait();
// to retrieve the first job in the list
int val = list.removeFirst();
System.out.println("Consumer consumed-"
+ val);
// Wake up producer thread
notify();
// and sleep
Thread.sleep(1000);
}
}
}
}
}
输出:
Producer produced-0
Producer produced-1
Consumer consumed-0
Consumer consumed-1
Producer produced-2
要点
- 在PC 类(具有生产和消费方法的类)中,添加了作业的链表和列表的容量,以检查生产者是否在列表已满时不生产。
- 在Producer 类中,该值被初始化为 0。
- 此外,我们有一个无限外循环来在列表中插入值。在这个循环中,我们有一个同步块,因此一次只运行一个生产者或消费者线程。
- 在将作业添加到列表之前存在一个内部循环,用于检查作业列表是否已满,生产者线程放弃 PC 上的内在锁定并进入等待状态。
- 如果列表为空,则控制传递到循环下方,并在列表中添加一个值。
- 在Consumer 类中,我们再次有一个无限循环来从列表中提取一个值。
- 在内部,我们还有一个内部循环,用于检查列表是否为空。
- 如果它是空的,那么我们让消费者线程放弃对 PC 的锁定,并将控制权传递给生产者线程以生产更多的工作。
- 如果列表不为空,我们将循环并从列表中删除一个项目。
- 在这两种方法中,我们在所有语句的末尾都使用了 notify。原因很简单,一旦你在列表中有东西,你可以让消费者线程消费它,或者如果你消费了一些东西,你可以让生产者生产一些东西。
- 两种方法末尾的 sleep() 只是使程序的输出以逐步方式运行,而不是一次显示所有内容,以便您可以看到程序中实际发生的情况。
练习:
- 建议读者使用 if 条件代替内部循环来检查边界条件。
- 尝试让您的程序产生一个项目,然后立即让消费者在生产者生产任何其他项目之前消费它。
参考 - https://en.wikipedia.org/wiki/Producer%E2%80%93consumer_problem