实验一 生产者和消费者问题
一、实验目的和要求
1)、通过编写程序,掌握基本的同步互斥算法,理解生产者和消费者模型。
2)、了解多线程并发执行机制,线程间的同步和互斥。
3)、学习使用同步对象,掌握相应的API。
二、实验主要内容
编写基础的生产者消费者程序,程序反映了生产者和消费者的工作过程,其中缓冲区只存放一类产品,生产者消费者只对一类产品进行操作。
1、创建生产者和消费者线程
2、生产和消费规则
①系统中有多个生产者,生产者每次只生产一种产品;
②系统中有多个消费者,消费者每次可以消费一个产品;
③生产者与消费者共享一个具有n个缓冲区的缓冲池;
④生产者与消费者互斥使用缓冲池,即某一时刻只允许一个生产者或消费者使用缓冲池。
⑤不允许消费者进程到一个空缓冲池去取产品;也不允许生产者进程向一个已装满产品且尚未被取走的缓冲池中投放产品。
⑥缓冲池不要求是循环环形缓冲区,也不要求一定是顺序访问,生产者可以将产品投放入任意的空缓冲区。
三、实验仪器设备:PC兼容机1台
四、程序流程图
五、实验源代码
publicclassOSLab1 {
publicstaticvoidmain(String[] args) {
BufferPool bufferPool =newBufferPool();// 新建一个缓冲池对象 bufferPool
Producer producer1 =newProducer("生产者A", bufferPool);// 新建一个生产者对象producer1
Producer producer2 =newProducer("生产者B", bufferPool);// 再新建一个生产者对象producer2
Consumer consumer1 =newConsumer("消费者1", bufferPool);// 新建一个消费者对象consumer1
Consumer consumer2 =newConsumer("消费者2", bufferPool);// 再新建一个消费者对象consumer2
Thread proT1 =newThread(producer1);// 从 producer1对象新建一个生产者线程 proT1
Thread proT2 =newThread(producer2);// 从producer1对象新建一个生产者线程 proT2
Thread conT1 =newThread(consumer1);// 从consumer1对象新建一个消费者线程 conT2
Thread conT2 =newThread(consumer2);// 从consumer2对象新建一个消费者线程 conT2
proT1.start();// 使 proT1线程处于可运行状态
proT2.start();// 使 proT2线程处于可运行状态
conT1.start();// 使 conT1线程处于可运行状态
conT2.start();// 使 conT2线程处于可运行状态
}
}
classBufferPool {
String[]buf;// 字符串数组作为内部存储结构
intfront,rear;// 队头指针和队尾指针
intsize;// 数组容量,采用循环队列时实际容量为size-1
intcount= 0;// 创建缓冲区,用来放产品
publicBufferPool() {// 默认构造方法
this(10);// 调用其他构造方法
}
publicBufferPool(intsize) {// 指定容量的构造方法
this.size= size;// 数组容量
buf=newString[size];// 创建数组
front= 0;// 队头指向数组第0个元素
rear= 0;// 队尾指向数组第0个元素
}
// 向缓冲池投放产品
publicsynchronizedvoidputItem(Producer pro, String message) {
while((rear+ 1) %size==front)// 队列为满
{
System.out.println("仓库已满, "+ pro.getProducerName() +"正等待生产...");
try{
this.wait();// 队列为满时,线程运行到此,将进入等待状态
}catch(InterruptedException e) {
System.out.println("BufferPool中出现了异常!");
}
}
buf[rear] = message;// 放入一个产品
rear= (rear+ 1) %size;// 队尾指针后移
count++;// 计数器加1
System.out.println(pro.getProducerName() +" 生产了 1个产品, 库存为"+count);
notifyAll();// 唤醒等待的线程
}
// 从缓冲池取走产品
publicsynchronizedString getItem(Consumer con) {
while(rear==front) {// 队列为空
try{
System.out.println("仓库数量不足, 消费者等待消费...");
this.wait();// 队列为空时,线程运行到此,将进入等待状态
}catch(InterruptedException e) {
System.out.println("BufferPool中出现了异常!");
}
}
String Item =buf[front];// 从缓冲区读取一条消息
front= (front+ 1) %size;// 队头指针后移
count=count- 1;// 计数器减1
System.out.println(con.getConsumerName() +" 消费了 1个产品, 库存为"+count);
notifyAll();// 唤醒等待的线程
returnItem;// 返回产品
}
}
// 定义生产者线程
classProducerimplementsRunnable {
privateStringproducerName=null;
privateBufferPoolbufferPool=null;
// 声明生产者要放产品的缓冲池
publicProducer(String producerName, BufferPool bufferPool) {
this.producerName= producerName;
this.bufferPool= bufferPool;
}
publicvoidsetProducerName(String producerName) {
this.producerName= producerName;
}
publicString getProducerName() {
returnproducerName;
}
// 定义一个新线程必须重写父类的run方法
publicvoidrun() {// 线程的执行体
intcount = 0;// 产品的编号
while(true) {
String Item =producerName+"生产的第"+ (++count) +"个产品";
// 放入缓冲池,如果缓冲区满,当前线程进入等待状态
bufferPool.putItem(this, Item);
try{
Thread.sleep((int) (Math.random() * 2500));// 定义线程休眠时间
}
// 有可能抛出异常,必须对它进行捕捉
catch(InterruptedException e) {
System.out.println("Proceducer中出现了异常!");
}
}// End while
}
}
// 定义消费者线程
classConsumerimplementsRunnable {
publicStringconsumerName=null;
publicBufferPoolbufferPool=null;
// 声明消费者要放产品的缓冲池
publicConsumer(String consumerName, BufferPool bufferPool) {
this.consumerName= consumerName;
this.bufferPool= bufferPool;
}
publicvoidsetConsumerName(String consumerName) {
this.consumerName= consumerName;
}
publicString getConsumerName() {
returnconsumerName;
}
publicvoidrun() {// 线程的执行体
while(true) {
// 从缓冲区取走一个产品,如果缓冲区为空,当前线程进入等待状态
String Item =bufferPool.getItem(this);
if(Item !=null) {
System.out.println("Namely,"+consumerName+"消费了"+ Item
+".");// 取走产品
try{
Thread.sleep((int) (Math.random() * 5000));// 定义线程休眠时间
}catch(InterruptedException e) {
System.out.println("Consumer中出现了异常!");
}
}
}// End while
}
}
六、实验结果
七、实验心得
虽然在本实验上花费了不少时间,但通过查阅资料后,最终还是完成了实验要求。本实验虽没有像C++语言那样采用原语操作,但通过wait()、notifyAll和synchronized关键字的配合使用,也还是实现了进程同步与互斥。