Java SE多线程部分--21.等待唤醒机制
1、概述
等待唤醒机制就是用于解决线程间通信的问题、常见的方法如下:
1. wait:线程不再活动,不再参与调度,进入 wait set 中,因此不会浪费 CPU 资源,也不会去竞争锁了,
这时的线程状态即是 WAITING。它还要执行一个特别的动作,也即是“通知(notify)”
在这个对象上等待的线程从wait set 中释放出来,重新进入到调度队列(ready queue)中
2. notify:则选取所通知对象的 wait set 中的一个线程释放;例如,餐馆有空位置后,
等候就餐最久的顾客最先入座。
3. notifyAll:则释放所通知对象的 wait set 上的全部线程。
注意细节:
1. wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程。
2. wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的。
3. wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方法。
下面来说下生产者和消费者的问题 :
线程A用来生产了一产品,线程B用来消费了一个产品,产品可以理解为同一资源,线程A与线程B处理的动作,
一个是生产,一个是消费,那么线程A与线程B之间就要用到等待唤醒机制。
2、实例
例子 :以包子为例
生产线程已经生产完毕包子了, 此时, 不再生产, 而是等待消费线程来消费. 如果生产线程没有包子, 此时, 就需要生产, 生产完毕后, 唤醒消费线程来消费.
消费线程消费完毕包子后, 不再消费, 此时, 消费线程就需要唤醒生产线程来生产. 如果消费线程没有包子, 消费线程就进入到等待状态.
共享资源类 :
public class BaoZi {
// 属性
private String pier;
private String xianer;
// 类型
boolean type = false;
boolean flag = false;
public BaoZi(String pier, String xianer) {
this.pier = pier;
this.xianer = xianer;
}
public BaoZi() {
}
@Override
public String toString() {
return "BaoZi{" +
"pier='" + pier + '\'' +
", xianer='" + xianer + '\'' +
'}';
}
public String getPier() {
return pier;
}
public void setPier(String pier) {
this.pier = pier;
}
public String getXianer() {
return xianer;
}
public void setXianer(String xianer) {
this.xianer = xianer;
}
}
生产线程 :
public class BaoZiPu extends Thread {
// 属性 (记录数据)
private BaoZi baoZi;
// 构造方法
public BaoZiPu(BaoZi baoZi, String name) {
super(name);
this.baoZi = baoZi;
}
// 重写 Thread 类的 run 方法
@Override
public void run() {
// 不断生产
while (true) {
// 同步锁环境
synchronized (baoZi) {
// 生产功能
produce();
}
}
}
// 生产功能 : 子线程
public void produce() {
// 随机时间切换 : 0 ~ 500 毫秒 (long)(Math.random() * 500)
/*try {
Thread.sleep((long)(Math.random() * 500));
} catch (InterruptedException e) {
e.printStackTrace();
}*/
// 1. 有包子, 生产线程进入等待
if (baoZi.flag == true) {
try {
baoZi.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 2. 没有包子, 生产线程进行生产
// 2.1 判断数据类型, 赋值不同的数据
if (baoZi.type == false) {
// 大葱牛肉
baoZi.setPier("大葱");
baoZi.setXianer("牛肉");
} else {
// 金牌虾仁
baoZi.setPier("金牌");
baoZi.setXianer("虾仁");
}
// 2.2 更改类型 (类型取反即可)
baoZi.type = !baoZi.type;
// 2.3 输出语句
System.out.println(Thread.currentThread().getName() + " 生产了" + baoZi.getPier() + baoZi.getXianer() + " 的包子.");
// 2.4 更改包子的标记
baoZi.flag = true;
// 3. 唤醒消费线程
baoZi.notify();
}
}
消费线程 :
public class ChiHuo extends Thread {
// 属性 (记录数据)
private BaoZi baoZi;
// 构造方法
public ChiHuo(BaoZi baoZi, String name) {
super(name);
this.baoZi = baoZi;
}
// 重写 Thread 类的 run 方法
@Override
public void run() {
// 不断消费
while (true) {
synchronized (baoZi) {
// 消费功能
consume();
}
}
}
// 消费功能
public void consume() {
// 随机时间切换 : 0 ~ 500 毫秒 (long)(Math.random() * 500)
/*try {
Thread.sleep((long)(Math.random() * 500));
} catch (InterruptedException e) {
e.printStackTrace();
}*/
// 1. 判断, 没有包子, 死等
if (baoZi.flag == false) {
try {
baoZi.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 2.1 取出数据, 消费
System.out.println(Thread.currentThread().getName() + " 正在吃 " + baoZi.getPier() + baoZi.getXianer() + " 的包子.");
// 2.2 将数据清空
baoZi.setPier("null");
baoZi.setXianer("null");
// 2.3 修改包子的标记
baoZi.flag = false;
// 3. 唤醒生产线程生产包子
baoZi.notify();
}
}
测试类 :
public class ProduceAndConsumeTest {
public static void main(String[] args) {
// 准备一个共享数据类
BaoZi baoZi = new BaoZi();
// 生产线程
BaoZiPu baoZiPu = new BaoZiPu(baoZi, "生产线程");
// 消费线程
ChiHuo chiHuo = new ChiHuo(baoZi, "消费线程");
// 启动线程
baoZiPu.start();
chiHuo.start();
}
}