生产者消费者问题是著名的进程同步问题,它描述了一组生产者向另一组消费者提供产品,它们共享一个有界缓冲区,生产者向其中投放产品,消费者从中取得产品。同时,每个进程都互斥占用CPU。
假定生产者和消费者是互相等效的,只要缓冲池未满,生产者就可以把产品送到缓冲区,类似的只要缓冲区未空,消费者也可以从缓冲池取走产品并消费,生产者不能向满的缓冲区放入产品,消费者不能从空的缓冲池拿产品。
解决思路及代码:
得益于java的多线程技术,我们能够很好的模拟上述情形,经过查阅JDK发现:在Java中有种类叫:Semphore,这是个同步工具类,提供了一种计数信号量机制,有两个方法:acquire()和release(),分别用来“申请”和“释放”资源,也就是PV操作,
针对该问题,我们需要设置三个信号量:
mutex:互斥信号量,用于“互斥”控制访问缓冲池;
full:缓冲池空闲信号量,用于记录缓冲池中“满”的缓冲区数;
empty:空闲缓冲池信号量。用于记录缓冲池中“空”的缓冲区数;
对于mutex信号量,要保证缓冲池只能有一个进程,故其初始值设为“1”,对于full信号量,刚开始生产者未生产消费者未消费,故缓冲池满的缓冲区数设为“0”,对于empty信号量,其设置的大小取决于缓冲池的大小,针对该问题,我们先设置两个变量(该问题我们把缓冲池看为面包柜):
int boardNum =0; //用来记录面包柜里的数量
Int maxBoardNum=5; //表示面包柜所能容纳的最大面包数目;
紧接着,我们实现上述三个信号量的初始化:
Semaphore mutex = new Semaphore(1, true);
Semaphore empty = new Semaphore(maxBreadNum, true);
Semaphore full = new Semaphore(0, true);
接下来,我们设置两个类Producer(生产者)和Consumer(消费者),继承Thread线程类或实现Runnable接口来创建进程,并重写Run()方法:
// 生产者类
class Producer extends Thread {
public void run() {
while (true) {
try {
empty.acquire();
mutex.acquire();
System.out.print("生产者生产了一个面包");
breadNum++;
System.out.println("当前面包数量为:"+breadNum);
mutex.release();
full.release();
Thread.sleep(1000); // 模拟生产过程
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 消费者类
class Consumer extends Thread {
public void run() {
while (true) {
try {
full.acquire();
mutex.acquire();
System.out.print("消费者买到了一个面包,");
breadNum--;
System.out.println("当前面包数量为:"+breadNum);
mutex.release();
empty.release();
Thread.sleep(2000); // 模拟消费过程
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
接下来对上述代码进行分析:
Producer生产者想要往缓冲池中放入产品,首先要判断是否有“空”缓冲区,故需要对其进行申请资源的操作即P操作,在这里也就是acquire(empty),申请成功空缓冲区数-1,为零则设置为中断态;
Consumer消费者想要往缓冲池中取走产品,首先要判断是否有“满”缓冲区,故需要对其进行申请资源的操作即P操作,在这里也就是acquire(full),如果满缓冲区不为0则-1,为0则设置为中断态;
我们知道,PV操作都是成对出现的,即acquire()和release()是成对出现的,我们可以这么理解:面包柜是透明的,有多少面包(满缓冲区),有多少面包空位(空缓冲区)是一目了然的。
生产者申请空缓冲区(我看柜子里有空位置,生产一个面包往柜子里放),放完产品后,那么满缓冲区就应该被释放(放进去一个面包,柜子里能放的面包数就减少一个);
同理,消费者申请满缓冲区(我看柜子里有面包,从柜子中拿走一个吃吃),用完产品后,那么空缓冲区就应该被释放(我吃了一个面包,柜子里能放的面包数就减少多一);
对于互斥信号量mutex,可以理解为面包柜在一个狭小的玻璃房子中,一次只能进一个人,当里面有人时,第二个人是无法进去的。mutex初始值为1,进去一个人,acquire后mutex-1,当其他人再申请时就会被设置为中断态,你想进也进不去。
mutex申请必须在full和empty申请之后,很简单,如果我是消费者,我看见房子里没人我就进去了,进去一看但柜子里空空如也,acquire(full)把我置为中断态,后面的release(empty)也释放不了了,这时进程切换,后面的人想进房子里但是房子里有人,acquire(mutex)又把他置为中断态,又卡住了,如此往复,始终如此;但如果我是消费者,我透过玻璃一看柜子里没有面包,这时候我在外面等,可以自由的溜达,别人想进也能进,我就等着有面包我再去开门,这样就避免了死锁现象。
接下来就是在主函数写些代码来模拟一下:
由于我们创建的变量都是非静态变量,所以我们需要创建一个Main类的实例对象来调用非静态方法和成员变量:(这个方法我也是才学会,百度真是个好老师)
Main main= new Main();
创建生产者消费者进程并启动
Producer producer1 = main.new Producer();
Consumer consumer1 = main.new Consumer();
Producer1.start();
Consumer1.start();
如果要探讨单缓冲区问题把full值置为1即可;
如果要探讨多个生产者消费者问题,多创建几个producer和consumer进程即可;
在王道计算机操作系统复习指导中有道多生产者消费者生产不同产品的习题,问题描述如下:
爸爸生产苹果,妈妈生产香蕉,儿子只吃苹果,女儿只吃香蕉,只有一个盘子,上面只能放一个水果,盘子互斥访问:
该问题看作两个生产者两个消费者问题,爸爸妈妈之间互斥,儿子女儿互斥,具体如下:
具体实现如下:
String plateFood=null;
Semaphore mutex = new Semaphore(1,true);
Semaphore apple = new Semaphore(0,true);
Semaphore banana = new Semaphore(0,true);
class Father implements Runnable{
public void run(){
try {
while (true) {
mutex.acquire();
plateFood="苹果";
System.out.println("爸爸往盘子放了一个苹果,现在盘子里有"+plateFood);
Thread.sleep(1000);
apple.release();
}
}
catch(Exception e){
e.printStackTrace();
}
}
}
class Mother implements Runnable{
public void run(){
try {
while (true) {
mutex.acquire();
plateFood="香蕉";
System.out.println("妈妈往盘子放了一个香蕉,现在盘子里有"+plateFood);
Thread.sleep(1000);
banana.release();
}
}
catch(Exception e){
e.printStackTrace();
}
}
}
class Son implements Runnable{
public void run(){
try {
while (true) {
apple.acquire();
System.out.println("现在盘子里的"+plateFood+"被儿子吃了");
plateFood=null;
Thread.sleep(2000);
mutex.release();
}
}
catch(Exception e){
e.printStackTrace();
}
}
}
class Daughter implements Runnable{
public void run(){
try {
while (true) {
banana.acquire();
System.out.println("现在盘子里的"+plateFood+"被女儿吃了");
plateFood=null;
Thread.sleep(2000);
mutex.release();
}
}
catch(Exception e){
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Main main = new Main();
Father father = main.new Father();
Mother mother = main.new Mother();
Son son = main.new Son();
Daughter daughter = main.new Daughter();
new Thread(father).start();
new Thread(mother).start();
new Thread(son).start();
new Thread(daughter).start();
}
运行结果: