先决条件Java的信号量、进程间通信、使用信号量的生产者消费者问题 |设置 1
在计算中,生产者-消费者问题(也称为有界缓冲区问题)是多进程同步问题的典型例子。该问题描述了两个进程,生产者和消费者,它们共享一个公共的、固定大小的缓冲区,用作队列。
- 生产者的工作是生成数据,放入缓冲区,然后重新开始。
- 同时,消费者正在消费数据(即从缓冲区中删除它),一次一个。
问题:确保生产者不会在缓冲区已满时尝试将数据添加到缓冲区中,并且消费者不会尝试从空缓冲区中删除数据。
解决方案:如果缓冲区已满,生产者要么进入睡眠状态,要么丢弃数据。下一次消费者从缓冲区中删除项目时,它会通知生产者,生产者再次开始填充缓冲区。同样,如果消费者发现缓冲区为空,则可以进入睡眠状态。下一次生产者将数据放入缓冲区时,它会唤醒沉睡的消费者。
不适当的解决方案可能会导致两个进程都在等待唤醒的死锁。
在Java使用线程的生产者-消费者后解决方案中,我们已经通过使用线程间通信(wait(),notify(),sleep())讨论了上述解决方案。在这篇文章中,我们将使用信号量来实现相同的功能。
下面的解决方案由四个类组成:
- 问:您尝试同步的队列
- Producer :产生队列条目的线程对象
- Consumer :消耗队列条目的线程对象
- 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() 调用的顺序由两个信号量处理: semProd和semCon 。
- 在 put() 可以生产项目之前,它必须从 semProd 获得许可。在它生产了项目之后,它释放 semCon。
- 在 get() 可以消费一个项目之前,它必须从 semCon 获得许可。消耗完物品后,它会释放 semProd。
- 这种“给予和接受”机制确保每次调用 put() 之后都必须调用 get()。
- 还要注意 semCon 是在没有可用许可的情况下初始化的。这确保 put() 首先执行。设置初始同步状态的能力是信号量更强大的方面之一。