📜  在Java中使用线程的生产者-消费者解决方案

📅  最后修改于: 2022-05-13 01:54:46.467000             🧑  作者: Mango

在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