对于经典的生产者和消费者模型,我做出以下理解以及代码的测试:
1.线程的等待与唤醒适用于生产速度和消费速度不匹配的情况,这时候构造一个类似容器的东西,一方未完成的时候,另一方在此等待。等到另一方完成的时候将对方唤醒继续执行;好处是可以解耦生产者和消费者的关系,系统易于维护。
案例分析,想要得到的效果:
当前存在一个牛奶生产者和一个牛奶消费者,生产者把生产好的牛奶放入盒子中,消费者从盒子中取出。盒子最大能容纳5瓶牛奶,生产一瓶牛奶需要2秒钟,消费者消耗一瓶牛奶需要3秒钟。
这个时间是我自己考虑的设定,存在固定的时间差,能够更容易的看轻每一步的执行结果。
其次,消费者只需要5瓶牛奶。不设定上限的话,就是程序就是无限循环下去。
而生产者只要有需要就会一直生产;
//盒子的类
public class Box {
private int milk;
public Box(int milk) {
this.milk = milk;
}
public Box() {
}
public synchronized int getMilk() {
if(milk==0){
try {
System.out.println("没有牛奶,请等待");
//执行到此处的线程就进行等待
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
milk--;
System.out.println("取走第"+(milk+1)+"瓶牛奶");
//唤醒其他所有线程
notifyAll();
}
return milk;
}
public synchronized void setMilk(int p) {
if(milk==5){
System.out.println("牛奶已满,请等待");
try {
//执行到此处的线程就进行等待
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
milk++;
System.out.println("放入第"+milk+"瓶牛奶");
//唤醒其他所有线程
notifyAll();
}
}
}
//顾客类
public class Custemer implements Runnable {
private Box box;
public Custemer(Box box) {
this.box = box;
}
@Override
public void run() {
for (int i = 0; i < 6; i++) {
box.getMilk();
System.out.println("消费者循环执行次数" + (i + 1));
try {
//获取之后线程睡眠3秒
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//生产者类
public class Producer implements Runnable {
private Box box;
public Producer(Box box) {
this.box = box;
}
@Override
public void run() {
while (true){
box.setMilk(1);
System.out.println("正在生产牛奶,需2秒钟");
try {
//以线程睡眠代表生产时间
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//主函数测试类
public class Test01 {
public static void main(String[] args) {
Box b = new Box();
Producer p = new Producer(b);
Custemer c = new Custemer(b);
Thread tp = new Thread(p);
Thread tc = new Thread(c);
//启动线程
tc.start();
tp.start();
}
}
1.执行结果的逐步分析如下图:
总结:
wait()方法属于Object类,并不是任何一个线程属性,可以理解为一个标识,线程看到这个标识了就会自己进行等待。跟睡眠不同,睡眠是自己让自己睡眠,并且知道自己要睡多少,所以知道什么时候醒来。但是看到等待,却不知道自己要等多久,所以需要别人去唤醒他。
notify以及notifyall都可以唤醒线程。唤醒的时候并不能指定唤醒谁,要么随机唤醒一个,要么唤醒所有,建议多数情况下使用唤醒所有。