线程间通信,生产者消费者问题!

本文通过Java线程间通信实现生产者与消费者问题,介绍如何使用共享对象、wait与notify方法实现线程间的同步和通信。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

线程之间的通信简介:
一般而言,在一个应用程序中(即进程),一个线程往往不是孤立存在的,常常需要和其它线程通信,以执行特定的任务。如主线程和次线程,次线程与次线程,工作线程和用户界面线程等。这样,线程与线程间必定有一个信息传递的渠道。这种线程间的通信不但是难以避免的,而且在多线程编程中也是复杂和频繁的。
线程通信的目标是使线程间能够互相发送信号。另一方面,线程通信使线程能够等待其他线程的信号。
例如,线程B可以等待线程A的一个信号,这个信号会通知线程B数据已经准备好了,典型的例子就是生产者消费者问题,生产者生产了一定量的产品,消费者这边才拥有商品出售。本文将通过JAVA线程间通信来实现生产者与消费者问题,在此之前我们需要了解一下几个知识点:

1、通过共享对象通信(产品)

2、忙等待

3、wait(),notify()和notifyAll()

4、多线程等待相同信号

1.线程间通信的共享对象(Product)

Product类是生产者与消费者的共享类,是实现他们之间数据的共享对象。生产者生产Product,消费者消费Product,所以在Product类中,分别有一个make和一个sale方法。注意,在这两个方法中,都使用了synchronized关键字,实现线程安全机制。在每个方法中,还用到了wait和notify方法, Obj.wait(),与Obj.notify()必须要与synchronized(Obj)一起使用,也就是wait,与notify是针对已经获取了Obj锁进行操作,从语法角度来说就是Obj.wait(),Obj.notify必须在synchronized(Obj){...}语句块内。从功能上来说wait就是说线程在获取对象锁后,主动释放对象锁,同时本线程休眠。直到有其它线程调用对象的notify()唤醒该线程,才能继续获取对象锁,并继续执行。相应的notify()就是对对象锁的唤醒操作。但有一点需要注意的是notify()调用后,并不是马上就释放对象锁的,而是在相应的synchronized(){}语句块执行结束,自动释放锁后,JVM会在wait()对象锁的线程中随机选取一线程,赋予其对象锁,唤醒线程,继续执行。
具体代码如下:

<span style="font-size:18px;">import java.util.ArrayList;
import java.util.List;

public class Product {
	private String name;
	private int count;
	public static List<Product> list = new ArrayList<Product>();

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getCount() {
		return list.size();
	}

	public void setCount(int count) {
		this.count = count;
	}

	public static List<Product> getList() {
		return list;
	}

	public static void setList(List<Product> list) {
		Product.list = list;
	}

	// /生产方法
	public synchronized void make(Product p) {
		if (getCount() > 9) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		} else {
			this.notifyAll();
			list.add(p);
		}
	}

	// /消费
	public synchronized void sale() {
		if (getCount() < 1) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		} else {
			this.notifyAll();
			list.remove(0);
		}

	}
}
</span>

2.生产者类,生产者类实现了Runnable接口,所以是一个线程类。源码如下:

<span style="font-size:18px;">public class Maker implements Runnable {
	Product product = new Product();
	public Maker(Product product) {
		this.product = product;
	}
	public void run() {
		while (true) {
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			Product p = new Product();
			p.setName("aaa");
			product.make(p);
			System.out.println("生产:当前剩余产品-->" + product.getCount());
		}

	}

}
</span>

3.消费者类,消费者类也实现了Runnable接口。

<span style="font-size:18px;">public class Saler implements Runnable {
	
	Product product = new Product();
	public Saler(Product product) {
		this.product = product;
	}
	public void run() {
		while (true) {
			try {
				Thread.sleep(300);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			product.sale();
			System.out.println("消费:当前剩余产品-->" + product.getCount());
		}
	}
}
</span>


4.测试代码如下,在测试代码中,分别创建了四个生产者线程和两个消费者线程。
<span style="font-size:18px;">public class Test {
	public static void main(String[] args) {
		Product product = new Product();
		Maker maker = new Maker(product);
		Saler saler = new Saler(product);
		Thread m1 = new Thread(maker);
		Thread m2 = new Thread(maker);
		Thread m3 = new Thread(maker);
		Thread m4 = new Thread(maker);
		Thread s1 = new Thread(saler);
		Thread s2 = new Thread(saler);
		m1.start();
		m2.start();
		m3.start();
		m4.start();
		s1.start();
		s2.start();
	}
}
</span>

5.运行结果如果下,运行结果中我们可以看到,生产者生产商品的数量,不会超过10个,当商品数量超过10个的时候,生产者就会停止生产,而是等待消费者消费了一定量的商品,才会继续生产。同样,当剩余商品的数量小于0时,消费者也不会继续消费。这就实现了生产者与消费者间的线程通信,两者共享一个数据对象。





实验题目: 生产者消费者(综合性实验) 实验环境: 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();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值