等待唤醒机制
一、线程状态概述
在线程的生命周期中,一共有6种状态。
- NEW(新建状态):线程刚被创建,还未启动,没有调用start()方法
- Runnable(可运行):线程可以在java虚拟机中运行的状态
- Blocked(阻塞状态):当一个线程试图获取对象锁,而此时对象锁被其他线程占有时,就会进入阻塞状态;如果获取到了对象锁,就会进入Runnable状态
- Waiting(无线等待):一个线程在等待另一个线程执行一个唤醒(notify)动作时,就会进入无线等待状态,进入这个状态是不能自动唤醒的。
- Timed Waiting(计时等待):同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep、Object.wait。
- Teminated(被 终止):因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。
二、等待唤醒机制
- 线程之间的通信
- 多个线程在处理同一资源,但是处理的动作(线程任务)不同。
比如:线程A来生产包子,线程B来吃包子。包子可以理解为同一资源,线程A与线程B处理的动作,一个是生产、一个是消费,那么线程A与线程B之间就有线程通信问题。
-
为什么要处理线程之间的通信
多个线程在并发执行时,默认CPU是随机切换线程的,当我们需要多个线程有规律的来共同完成一项任务时,那么多线之间就需要一些协调通信。 -
如何保证线程之间通信有效的利用资源
多个线程在处理同一个资源,并且任务不同时,需要线程通信来帮助解决线程之间对同一个变量的使用或操作。 就是多个线程在操作同一份数据时, 避免对同一共享变量的争夺。也就是我们需要通过一定的手段使各个线程能有效的利用资源。而这种手段即—— 等待唤醒机制。 -
什么是等待唤醒机制
是多个线程键的一种协作机制。一般用到的方法有wait()方法或notify()方法
如果能获得锁,线程会进入Runnable状态;如果不能,线程进入Blocked状态。
- 调用wait()和notify()要注意的问题:
- wait方法与notify方法必须由同一锁对象调用。因为对应的锁对象可以通过调用notify方法唤醒使用同一锁对象调用wait方法之后的线程
- 两种方法是属于Object类的方法。因为锁对象可以是任意对象,而任意对象的所属类都继承于Object类
- 两种方法必须在同步代码块或同步方法中使用。因为必须通过锁对象调用这两个方法
三、案例:生产者与消费者问题
等待唤醒机制其实就是经典的“生产者与消费者”的问题。
包子铺线程生产包子,吃货线程消费包子。当包子的状态为false(没有)时,吃货线程等待,包子铺线程生产包子,并将包子的状态改为true,唤醒吃货线程吃包子。因为已经有包子了,所以包子铺线程进入等待状态。吃货线程能否吃到包子,取决于能不能获得锁对象。如果获得锁对象,就吃包子,并把包子状态改为false,并唤醒包子铺线程开始做包子,吃货线程进入等待状态。
代码演示:
包子资源类
public class BaoZi {
/*
* 包子的属性:包子皮、包子馅、包子的状态--默认false为没有*/
String pi;
String xian;
boolean flag=false;
}
包子铺线程类
public class BaoZiPu extends Thread{
private BaoZi bz;
public BaoZiPu(BaoZi bz) {
this.bz = bz;
}
//重写run方法,设置线程任务
@Override
public void run() {
int count=0;
//放入同步代码块中,保证一次只有一个线程
while (true){
synchronized (bz){
//如果有包子,进入等待状态
if(bz.flag==true){
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//被唤醒之后要执行的代码,包子铺开始做包子
//没有进行生产包子,判断数量
if(count%2==0){
bz.pi="薄皮";
bz.xian="莲菜大肉";
}else{
bz.pi="水晶皮";
bz.xian="西红柿鸡蛋";
}
//生产一个数量加1
count++;
System.out.println("包子铺正在生产"+bz.pi+bz.xian+"的包子...");
//生产包子需要5秒钟
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//修改包子的状态为true
bz.flag=true;
//唤醒吃货线程池包子
bz.notify();
System.out.println("包子铺做好包子了,可以开始吃了...");
}
}
}
}
吃货线程类
//创建吃货类
public class ChiHuo extends Thread{
//使用包子作为锁对象
private BaoZi bz;
public ChiHuo(BaoZi bz) {
this.bz = bz;
}
//重写run方法,设置线程任务
@Override
public void run() {
while (true){
//使用同步代码块
synchronized (bz){
//先判断有没有包子,如果没有进入等待状态
if(bz.flag==false){
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//被唤醒之后执行的代码啊,吃货开始吃包子
System.out.println("吃货正在吃"+bz.pi+bz.xian+"的包子....");
//修改包子的状态为没有 false
bz.flag=false;
//吃货唤醒包子铺线程,继续生产包子
bz.notify();
System.out.println("吃货已经吃完了"+bz.pi+bz.xian+"的包子,包子铺开始生产包子");
System.out.println("---------------------------------");
}
}
}
}
测试类
//实例化包子对象
BaoZi bz=new BaoZi();
//包子铺线程对象
new BaoZiPu(bz).start();
//吃货线程对象
new ChiHuo(bz).start();
运行结果