为了保证线程间能够进行数据和逻辑的交互,就引入了一种【等待唤醒机制】
案例分析
详细过程:
需求分析:
代码实现
public class BaoZi {
//包子皮
String pi;
//包子馅
String xian;
//包子有没有,初始状态为没有
boolean flag = false;
}
public class BaoZiPu extends Thread{
//把包子对象作为锁对象
private BaoZi bz;
public BaoZiPu(BaoZi bz){
this.bz = bz;
}
@Override
public void run() {
int count = 0;//定义一个变量用来循环做不同的包子
synchronized (bz){
while(bz.flag){
//有包子就进入WAITING状态
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//没有包子就开始做包子
if(count%2 == 0){
bz.pi = "薄皮";
bz.xian = "三鲜馅";
}else{
bz.pi = "冰皮";
bz.xian = "韭菜馅";
}
count++;
System.out.println("包子铺正在生产"+bz.pi+bz.xian+"包子!");
//做包子需要花3秒时间
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(bz.pi+bz.xian+"包子已经做好了,可以开吃了!");
bz.flag = true;//修改包子状态为有
bz.notify();//唤醒顾客
}
}
}
public class BaoZiEat extends Thread{
private BaoZi bz;
public BaoZiEat(BaoZi bz){
this.bz = bz;
}
@Override
public void run() {
synchronized (bz){
while(!bz.flag){
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("吃货正在吃"+bz.pi+bz.xian+"包子");
System.out.println(bz.pi+bz.xian+"包子吃完了!");
bz.flag = false;
bz.notify();
System.out.println("=========================");
}
}
}
public class BaoZiDemo {
public static void main(String[] args) {
BaoZi bz = new BaoZi();
new BaoZiPu(bz).start();
new BaoZiEat(bz).start();
}
}
这里有个问题一定要十分注意:为什么要在两个线程所在类的构造方法中都传递BaoZi变量?下面这两种做法是不对的:
- 在两个线程中分别直接创建包子对象BaoZi bz = new BaoZi();在测试的时候分别new BaoZiPu(new BaoZi).start(),以及new BaoZiEat(new BaoZi).start()
- 为什么是错的呢?notify只能唤醒“同对象wait”的线程,如果有两个BaoZi对象,那么锁对象不一样就无法唤醒了。
对于同一个资源,两个线程一个是醒的另一个就在等待,另一个醒了这一个又在等待……保证了有规律地运行。
如果要想让线程重复执行,可以将【包含synchronized在内的代码】包裹在死循环中。注意千万别把死循环写在了synchronized内部,否则将永远出不了这个线程了。