一、问题描述
一组生产者进程和一组消费者进程共享一个初始为空、大小n的缓冲区,只有缓冲区没满时,生产者才能把资源放入缓冲区,否则必须等待;只有缓冲区不为空时,消费者才能从中取出资源,否则必须等待。由于缓冲区是临界资源,它只允许一个生产者放入资源,或一个消费者从中取出资源。
二、问题分析
(1)、关系分析。生产者和消费者对缓冲区互斥访问是互斥关系,同时生产者和消费者又是一个相互协作的关系,只有生产者生产之后,消费者只能才能消费,它们还是同步关系。
(2)、整理思路。只有生产生产者和消费者进程,正好是这两个进程存在着互斥关系和同步关系,即需要解决的是互斥和同步 PV 操作的位置。
(3)、信号量设置。信号量 mutex 作为互斥信号量,用于控制互斥访问缓冲池,互斥信号量初值为1;信号量 full 用于记录当前缓冲池中的“满”缓冲池,初值为0;信号量 empty 用于记录当前缓冲池中“空“缓冲区数,初值为n。
三、代码实现
import java.util.Scanner;
public class ProCon {
public static void main(String[] args) {
int producer,consumer;
Scanner sc=new Scanner(System.in);
System.out.print("请输入生产者数目:");
producer=sc.nextInt();//输入生产者数量
System.out.print("请输入消费者数目:");
consumer=sc.nextInt();//输入消费者数量
for(int i=0;i<producer;i++)
{
new Thread(new Producer(), "生产者" + Integer.toString(i) + "号").start();//创建生产者线程并开启
}
for(int j=0;j<consumer;j++)
{
new Thread(new Consumer(), "消费者" + Integer.toString(j) + "号").start();//创建消费者线程并开启
}
}
}
class Global
{
public static Semaphore empty=new Semaphore(3);//空闲缓冲区初始化为三
public static Semaphore full=new Semaphore(0);//满缓冲区初始化为空
public static Semaphore mutex=new Semaphore(1);//临界区互斥信号量
public static int count=0;//count用于缓冲区中的进程进行计数
//定时等待
public static void timingwait()
{
try
{
Thread.sleep(2000);//Thread.Sleep()方法用于将当前线程休眠一定时间 时间单位是ms,1s=1000ms
}catch(InterruptedException e)//当使用java.lang.Thread类的sleep方法时,可能会导致线程阻塞,需要抛出InterruptedException(中断异常)异常
{
e.printStackTrace();
}
}
}
//生产者
class Producer implements Runnable//Runnable接口创建新线程
{
@Override
public void run()//Runnable 接口可以被任何想要被一个线程运行的接口继承实现;继承 Runnable 接口的类必须有一个 run() 方法
{
Global.timingwait();
System.out.println(Thread.currentThread().getName() + " 生产出一个商品...");//Thread.currentThread().getName()获得当前执行的线程
Global.empty.P();//获取空缓冲区单元
Global.mutex.P();//进入临界区
Global.timingwait();
System.out.println(Thread.currentThread().getName() + " 将产品放入缓冲区--缓冲区剩余 " + (++Global.count) + " 个产品");
Global.mutex.V();//离开临界区,释放信号量
Global.full.V();//满缓冲区数加一
}
}
//消费者
class Consumer implements Runnable
{
@Override
public void run()
{
Global.timingwait();
Global.full.P();//获取满缓冲区单元
Global.mutex.P();//进入临界区
Global.timingwait();
System.out.println(Thread.currentThread().getName() + " 从缓冲区取出一个产品--缓冲区剩余 "+ (--Global.count) + " 个产品");
Global.mutex.V();//离开临界区,释放互斥信号量
Global.empty.V();//空缓冲区加一
System.out.println(Thread.currentThread().getName() + " 消费一个商品...");
}
}
//信号量
class Semaphore
{
public int value;
public Semaphore(int value)
{
super();
this.value=value;
}
//P操作
public synchronized final void P()//使用synchronized修饰的方法,叫做同步方法,保证A线程执行该方法的时,其他线程只能在方法外等着.
{//被final修饰的方法是一个最终方法,不能被重写,重写会报错
value--;
if(value<0)
{
try
{
this.wait();//当缓冲区已满/空时,生产者或消费者线程停止自己的执行,释放锁,使自己处于等待状态,让其它线程执行
}catch(InterruptedException e)//当使用java.lang.Thread类的 wait方法时,可能会导致线程阻塞,需要抛出InterruptedException(中断异常)异常
{
e.printStackTrace();
}
}
}
//V操作
public synchronized final void V()
{
value++;
if(value<=0)
{
this.notify();//当生产者或消费者向缓冲区放入或取出一个产品时,向其他等待的线程发出通知,同时释放锁,使自己处于等待状态,让其它线程执行。
}
}
}
四、运行效果
(运行结果是以缓冲区大小为3,消费者数量为2和生产者数量为3为初始值)
五、知识总结
1、该类问题需要注意对缓冲区大小为n的处理,当缓冲区中有空时,便可对 empty 变量执行 P 操作,一旦取走一个资源便可执行 V 操作以释放空闲区。对 empty 和 full 变量的 P 操作 必须放在 mutex 的P操作之前。
2、P操作即wait操作表示进程请求一个资源,V操作即signal表示进程释放一个资源,且信号量机制遵循了同步机制的“让权等待”原则。
3、wait():当缓冲区已满/空时,生产者或消费者线程停止自己的执行,释放锁,使自己处于等待状态,让其它线程执行。
notify():当生产者或消费者向缓冲区放入或取出一个产品时,向其他等待的线程发出通知,同时释放锁,使自己处于等待状态,让其它线程执行。
wait()、nofity()这两个方法必须有锁对象调用,而任意对象都可以作为 synchronized 的同步锁。
4、使用synchronized修饰的方法,其他线程只能在方法外等待。
被final修饰的方法是一个最终方法,不能被重写,重写会报错。
原文地址:https://blog.csdn.net/qq_44111805
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。