JAVA 操作系统:进程同步机制模拟-生产者消费者问题

生产者消费者问题是著名的进程同步问题,它描述了一组生产者向另一组消费者提供产品,它们共享一个有界缓冲区,生产者向其中投放产品,消费者从中取得产品。同时,每个进程都互斥占用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();

    }

运行结果:

 

 

  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
实验题目: 生产者与消费者(综合性实验) 实验环境: C语言编译器 实验内容: ① 由用户指定要产生的进程及其类别,存入进入就绪队列。    ② 调度程序从就绪队列中提取一个就绪进程运行。如果申请的资源被阻塞则进入相应的等待队列,调度程序调度就绪队列中的下一个进程。进程运行结束时,会检查对应的等待队列,激活队列中的进程进入就绪队列。运行结束的进程进入over链表。重复这一过程直至就绪队列为空。    ③ 程序询问是否要继续?如果要转直①开始执行,否则退出程序。 实验目的: 通过实验模拟生产者与消费者之间的关系,了解并掌握他们之间的关系及其原理。由此增加对进程同步的问题的了解。 实验要求: 每个进程有一个进程控制块(PCB)表示。进程控制块可以包含如下信息:进程类标号、进程系统号、进程状态、进程产品(字符)、进程链指针等等。 系统开辟了一个缓冲区,大小由buffersize指定。 程序中有三个链队列,一个链表。一个就绪队列(ready),两个等待队列:生产者等待队列(producer);消费者队列(consumer)。一个链表(over),用于收集已经运行结束的进程 本程序通过函数模拟信号量的操作。 参考书目: 1)徐甲同等编,计算机操作系统教程,西安电子科技大学出版社 2)Andrew S. Tanenbaum著,陈向群,马红兵译. 现代操作系统(第2版). 机械工业出版社 3)Abranham Silberschatz, Peter Baer Galvin, Greg Gagne著. 郑扣根译. 操作系统概念(第2版). 高等教育出版社 4)张尧学编著. 计算机操作系统教程(第2版)习题解答与实验指导. 清华大学出版社 实验报告要求: (1) 每位同学交一份电子版本的实验报告,上传到202.204.125.21服务器中。 (2) 文件名格式为班级、学号加上个人姓名,例如: 电子04-1-040824101**.doc   表示电子04-1班学号为040824101号的**同学的实验报告。 (3) 实验报告内容的开始处要列出实验的目的,实验环境、实验内容等的说明,报告中要附上程序代码,并对实验过程进行说明。 基本数据结构: PCB* readyhead=NULL, * readytail=NULL; // 就绪队列 PCB* consumerhead=NULL, * consumertail=NULL; // 消费者队列 PCB* producerhead=NULL, * producertail=NULL; // 生产者队列 over=(PCB*)malloc(sizeof(PCB)); // over链表 int productnum=0; //产品数量 int full=0, empty=buffersize; // semaphore char buffer[buffersize]; // 缓冲区 int bufferpoint=0; // 缓冲区指针 struct pcb { /* 定义进程控制块PCB */ int flag; // flag=1 denote producer; flag=2 denote consumer; int numlabel; char product; char state; struct pcb * processlink; …… }; processproc( )--- 给PCB分配内存。产生相应的的进程:输入1为生产者进程;输入2为消费者进程,并把这些进程放入就绪队列中。 waitempty( )--- 如果缓冲区满,该进程进入生产者等待队列;linkqueue(exe,&producertail); // 把就绪队列里的进程放入生产者队列的尾部 void signalempty() bool waitfull() void signalfull() void producerrun() void comsuerrun() void main() { processproc(); element=hasElement(readyhead); while(element){ exe=getq(readyhead,&readytail); printf("进程%d申请运行,它是一个",exe->numlabel); exe->flag==1? printf("生产者\n"):printf("消费者\n"); if(exe->flag==1) producerrun();

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值