生产者与消费者模型(唤醒机制实现线程之间的通讯)
文章目录
前言
项目场景:生产者,消费者两种对象。只有生产者的生产商品和消费者消费的商品达到平衡,才不会出现供大于求或供不应求的场景。
解决方法:采用同步的方法,使得多个线程之间达到平衡,或者说是线程之间能够通信。
举个例子:一个厨师如果不跟顾客沟通,一个劲的做饭,那是不是造成了浪费。 一个顾客也是,如果不跟初始沟通,自己要吃啥,吃多少,那岂不是自己的胃就任人摆布啦。也不至于你人在家里,厨师在饭店,你想吃他在做但是就是吃不到。于是这里就需要一个通信机制来解决这些问题啦。
一、同步是什么(搜集了各种解释)?
同步(协同步调):多个线程开始运行时,受到操作系统,硬件等等一系列的因素的制约,运行时顺序是不可控的!!!通过程序控制多线程的运行顺序或者步骤,就是同步!
同步:同步是指一个进程在执行某个请求的时候,如果该请求需要一段时间才能返回信息,那么这个进程会一直等待下去,直到收到返回信息才继续执行下去。就是必须一件一件事做,等前一件做完了才能做下一件事
同步:当程序1调用程序2时,程序1停下不动,直到程序2完成回到程序1来,程序1才继续执行下去。
同步:发送方发出数据后,等接收方发回响应以后才发下一个数据包的通讯方式。
二、如何实现同步
1.加锁
加锁是一种实现同步的手段,但是效率不高。
2.唤醒机制
2.1:一般是用在两个线程之间,运行时顺序是不可而控的!!!
使用唤醒机制实现同不可控。
2.2:使用共有的对象作为唤醒的对象存在(盘子)
2.3:必须要加同步锁(synchronized)
2.4:同步锁的钥匙必须是共有对象
wait() 让线程进入等待状态
notify() 唤醒线程
notifyALL() 唤醒所有线程
3.守护线程(补充知识点)
他所依赖的线程结束,他就结束。
让顾客依赖于厨师。
解决了顾客线程陷入死循环关不掉的问题。
thread(依赖线程的名称).setDaemon(true);
三.代码实现
1.盘子(作为中间介质,共有对象,来连接多个线程之间的通信)
public class Disk {
//盘子上面的食物
private String foodname;
//判断盘子是否为空
private boolean full;
public String getFoodname() {
return foodname;
}
public void setFoodname(String foodname) {
this.foodname = foodname;
}
public boolean isFull() {
return full;
}
public void setFull(boolean full) {
this.full = full;
}
public Disk() {
super();
}
public Disk(String foodname, boolean full) {
super();
this.foodname = foodname;
this.full = full;
}
}
2.顾客
public class Customer implements Runnable{
private Disk disk;
public Disk getDisk() {
return disk;
}
public void setDisk(Disk disk) {
this.disk = disk;
}
//构造函数
public Customer(Disk disk) {
super();
this.disk = disk;
}
public Customer() {
super();
}
@Override
public void run() {
// TODO Auto-generated method stub
eat();
}
private void eat() {
// TODO Auto-generated method stub
while(true)
//死循環,一直用於判斷盤子内是否有實物。
{
//
//2.3必须加同步锁,且2.4同步锁的钥匙必须是共有对象
synchronized (this.disk) {
if(this.disk.isFull())
{
//只要盤子裏面有東西,就一直吃東西。
System.out.println(Thread.currentThread().getName()+"吃了"+this.disk.getFoodname());
//吃完后盤子為空
this.disk.setFull(false);
//唤醒机制的使用
this.disk.notify();
try {
this.disk.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
else {
try {
this.disk.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
}
3.厨师
import java.util.Random;
public class Producter implements Runnable{
private String[] foods;
private Disk disk;
private Random random;
public String[] getFoods() {
return foods;
}
public void setFoods(String[] foods) {
this.foods = foods;
}
public Random getRandom() {
return random;
}
public void setRandom(Random random) {
this.random = random;
}
public Producter(Disk disk) {
super();
this.foods = new String[]{"牛肉面","热干面","米线","拉面"};
this.disk =disk;
this.random=new Random();
}
public Producter() {
super();
}
@Override
public void run() {
// TODO Auto-generated method stub
cookie();
}
private void cookie()
{
// TODO Auto-generated method stub
for(int i=0;i<10;i++)
{
synchronized (this.disk) {
//做饭也是有条件的,只有盘子是空的时候。才做饭
if(!this.disk.isFull())
{
//随机做一种食物
int index=this.random.nextInt(this.foods.length);
String food=this.foods[index];
System.out.println(Thread.currentThread().getName()+"制作了一种食物"+food);
this.disk.setFoodname(food);
this.disk.setFull(true);
this.disk.notify();
try {
this.disk.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
else {
try {
this.disk.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
}
测试类
public class test {
public static void main(String[] args) {
Disk disk=new Disk();
//保證顧客和厨師使用的是同一個盤子
Producter producter =new Producter(disk);
Customer customer=new Customer(disk);
Thread thread1= new Thread(producter);
Thread thread2=new Thread(customer);
//将顾客做成守护线程,让他依赖厨师。解决了顾客线程陷入死循环关不掉的问题
thread2.setDaemon(true);
thread1.start();
thread2.start();
}
}
总结
通过对生产者与消费者模型的不断完善,从刚开始解决线程关不掉,采用守护线程巧妙化解。然后又出现了线程不同步问题,采用唤醒机制。必须要有一个共同的对象来唤醒他们。顺利解决了牛头不对马嘴的问题。