📜  Java使用信号量的生产者-消费者解决方案2套

📅  最后修改于: 2021-09-28 09:27:39             🧑  作者: Mango

先决条件Java的信号量、进程间通信、使用信号量的生产者消费者问题 |设置 1

在计算中,生产者-消费者问题(也称为有界缓冲区问题)是多进程同步问题的典型例子。该问题描述了两个进程,生产者和消费者,它们共享一个公共的、固定大小的缓冲区,用作队列。

  • 生产者的工作是生成数据,放入缓冲区,然后重新开始。
  • 同时,消费者正在消费数据(即从缓冲区中删除它),一次一个。

问题:确保生产者不会在缓冲区已满时尝试将数据添加到缓冲区中,并且消费者不会尝试从空缓冲区中删除数据。

解决方案:如果缓冲区已满,生产者要么进入睡眠状态,要么丢弃数据。下一次消费者从缓冲区中删除项目时,它会通知生产者,生产者再次开始填充缓冲区。同样,如果消费者发现缓冲区为空,则可以进入睡眠状态。下一次生产者将数据放入缓冲区时,它会唤醒沉睡的消费者。
不适当的解决方案可能会导致两个进程都在等待唤醒的死锁

在Java使用线程的生产者-消费者后解决方案中,我们已经通过使用线程间通信(wait(),notify(),sleep())讨论了上述解决方案。在这篇文章中,我们将使用信号量来实现相同的功能。

下面的解决方案由四个类组成:

  1. :您尝试同步的队列
  2. Producer :产生队列条目的线程对象
  3. Consumer :消耗队列条目的线程对象
  4. PC :创建单个 Q、生产者和消费者的驱动程序类。
// Java implementation of a producer and consumer
// that use semaphores to control synchronization.
  
import java.util.concurrent.Semaphore;
  
class Q {
    // an item
    int item;
  
    // semCon initialized with 0 permits
    // to ensure put() executes first
    static Semaphore semCon = new Semaphore(0);
  
    static Semaphore semProd = new Semaphore(1);
  
    // to get an item from buffer
    void get()
    {
        try {
            // Before consumer can consume an item,
            // it must acquire a permit from semCon
            semCon.acquire();
        }
        catch (InterruptedException e) {
            System.out.println("InterruptedException caught");
        }
  
        // consumer consuming an item
        System.out.println("Consumer consumed item : " + item);
  
        // After consumer consumes the item,
        // it releases semProd to notify producer
        semProd.release();
    }
  
    // to put an item in buffer
    void put(int item)
    {
        try {
            // Before producer can produce an item,
            // it must acquire a permit from semProd
            semProd.acquire();
        }
        catch (InterruptedException e) {
            System.out.println("InterruptedException caught");
        }
  
        // producer producing an item
        this.item = item;
  
        System.out.println("Producer produced item : " + item);
  
        // After producer produces the item,
        // it releases semCon to notify consumer
        semCon.release();
    }
}
  
// Producer class
class Producer implements Runnable {
    Q q;
    Producer(Q q)
    {
        this.q = q;
        new Thread(this, "Producer").start();
    }
  
    public void run()
    {
        for (int i = 0; i < 5; i++)
            // producer put items
            q.put(i);
    }
}
  
// Consumer class
class Consumer implements Runnable {
    Q q;
    Consumer(Q q)
    {
        this.q = q;
        new Thread(this, "Consumer").start();
    }
  
    public void run()
    {
        for (int i = 0; i < 5; i++)
            // consumer get items
            q.get();
    }
}
  
// Driver class
class PC {
    public static void main(String args[])
    {
        // creating buffer queue
        Q q = new Q();
  
        // starting consumer thread
        new Consumer(q);
  
        // starting producer thread
        new Producer(q);
    }
}

输出:

Producer produced item : 0
Consumer consumed item : 0
Producer produced item : 1
Consumer consumed item : 1
Producer produced item : 2
Consumer consumed item : 2
Producer produced item : 3
Consumer consumed item : 3
Producer produced item : 4
Consumer consumed item : 4

说明:如您所见,对put()get()的调用是同步的,即每次调用 put() 之后都会调用 get(),并且不会遗漏任何项目。如果没有信号量,对 put() 的多次调用就会发生而没有匹配对 get() 的调用,从而导致项目丢失。 (为了证明这一点,删除信号量代码并观察结果。)

put() 和 get() 调用的顺序由两个信号量处理: semProdsemCon

  • 在 put() 可以生产项目之前,它必须从 semProd 获得许可。在它生产了项目之后,它释放 semCon。
  • 在 get() 可以消费一个项目之前,它必须从 semCon 获得许可。消耗完物品后,它会释放 semProd。
  • 这种“给予和接受”机制确保每次调用 put() 之后都必须调用 get()。
  • 还要注意 semCon 是在没有可用许可的情况下初始化的。这确保 put() 首先执行。设置初始同步状态的能力是信号量更强大的方面之一。