等待唤醒机制
1.1 线程间通信
概念: 多个线程在处理同一个资源,但是处理的动作(线程任务)却不同,那么多线程之间就存在通信问题。
比如:线程A用来生产包子的,线程B用来吃包子的,包子可以理解为同一个资源,线程A与线程B处理动作,一个是生产,一个是消费,那么线程A和线程B之间就存在线程通信问题。
为什么要处理线程间通信:
多个线程并发执行,在默认情况下线程的调度是抢占式的,当我们需要多个线程来完成一个任务,并且我们希望他们有规律的执行,那么多线程之间需要一些协调通信,以此来达到多线程共同操作同一份数据。
如何保证线程间通信有效利用资源:
多个线程在处理同一个资源,并且任务不同时,需要线程间通信来帮助解决线程间对同一个变量的使用或者操作。就是多个线程在操作同一份数据时,避免对同一共享变量的争夺。也就是我们需要通过一定的手段使各个线程间能有效的利用资源.而这种手段即——等待唤醒机制。
1.2 等待唤醒机制
什么是等待唤醒机制:
这是多线程间的一种协作机制。就是在一个线程进行了规定操作后,就进入等待状态(wait()),等待其他线程执行完它们指定代码后再将其唤醒(notify());在有多个线程进行等待时,如果需要,可以使用notifyAll()来唤醒所有等待线程.
wait/notify 就是线程间的一种协作机制.
等待唤醒中的方法
等待唤醒机制就是用于解决线程间通信问题的,使用到的3个方法的含义如下:
- wait() : 线程不在活动,不再参与调度,进入wait set中,音痴不会让费cpu资源,也不会去竞争锁,这时的线程状态是waiting.它还要执行一个特别的动作,也即是“通知(notify)”在这个对象上等待的线程从wait set 中释放出来,重新进入到调度队列(ready queue)中.
- notify:则选取所通知对象的 wait set 中的一个线程释放;例如,餐馆有空位置后,等候就餐最久的顾客最先入座。
- notifyAll:则释放所通知对象的 wait set 上的全部线程。
调用wait和notify方法需要注意的细节
- wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程。
- wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的。
- wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方法。
1.3 两个线程间的有效通信案例
- 生产者与消费者问题
就包子铺和消费者为例:
包子铺线程生产包子,吃货线程消费包子。
当包子没有时(包子状态为false),吃货线程等待,包子铺线程生产包子(即包子状态为true),并通知吃货线程(解除吃货的等待状态),因为已经有包子了,那么包子铺线程进入等待状态。
接下来,吃货线程能否进一步执行则取决于锁的获取情况。
如果吃货获取到锁,那么就执行吃包子动作,包子吃完(包子状态为false),并通知包子铺线程(解除包子铺的等待状态),吃货线程进入等待。
包子铺线程能否进一步执行则取决于锁的获取情况。
代码演示:
包子类资源:
public class BaoZi {
String pier ;
String xianer ;
boolean flag = false ;//包子资源 是否存在 包子资源状态
}
吃货线程类:
public class ChiHuo extends Thread{
private BaoZi bz;
public ChiHuo(String name,BaoZi bz){
super(name);
this.bz = bz;
}
@Override
public void run() {
while(true){
synchronized (bz){
if(bz.flag == false){//没包子
try {
bz.wait(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("吃货正在吃"+bz.pier+bz.xianer+"包子");
bz.flag = false;
bz.notify();
}
}
}
}
包子铺线程类:
public class BaoZiPu extends Thread {
private BaoZi bz;
public BaoZiPu(String name,BaoZi bz){
super(name);
this.bz = bz;
}
@Override
public void run() {
int count = 0;
//造包子
while(true){
//同步
synchronized (bz){
if(bz.flag == true){//包子资源 存在
try {
bz.wait(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 没有包子 造包子
System.out.println("包子铺开始做包子");
if(count%2 == 0){
// 面皮 三鲜
bz.pier = "面皮";
bz.xianer = "三鲜";
}else{
// 薄皮 牛肉
bz.pier = "薄皮";
bz.xianer = "牛肉";
}
count++;
bz.flag=true;
System.out.println("包子造好了:"+bz.pier+bz.xianer);
System.out.println("吃货来吃吧");
//唤醒等待线程 (吃货)
bz.notify();
}
}
}
}
测试类:
public class Demo {
public static void main(String[] args) {
//等待唤醒案例
BaoZi bz = new BaoZi();
ChiHuo ch = new ChiHuo("吃货",bz);
BaoZiPu bzp = new BaoZiPu("包子铺",bz);
ch.start();
bzp.start();
}
}
执行效果:
包子铺开始做包子
包子造好了:面皮三鲜
吃货来吃吧
吃货正在吃面皮三鲜包子
包子铺开始做包子
包子造好了:薄皮牛肉
吃货来吃吧
吃货正在吃薄皮牛肉包子
包子铺开始做包子
包子造好了:面皮三鲜
吃货来吃吧
吃货正在吃面皮三鲜包子
1.4三个线程间的有效通信案例
多个线程想要实现同步,多个线程必须加同一把锁 --->实现线程安全
实现有效通信: 让A,B,C线程交替执行
如果A线程在执行,那么BC线程就只能进入无线等待,然后A执行完了,就得唤醒所有等待线程
如果B线程在执行,那么AC线程就只能进入无线等待,然后B执行完了,就得唤醒所有等待线程
如果C线程在执行,那么AB线程就只能进入无线等待,然后C执行完了,就得唤醒所有等待线程
如果A线程执行完毕,那么必须让B线程抢到,即使AC抢到也得进入无限等待
B执行完之后,唤醒所有线程,必须让C线程抢到,即使AB抢到了也得进入无限等待
C执行完之后,唤醒所有线程,必须让A线程抢到,即使BC抢到了也得进入无限等待
A线程执行完毕,那么必须让B线程抢到,即使AC抢到也得进入无限等待
.....
砖类:
public class Zhuan {
int num ;
}
砖厂:
public class ZhuanCang implements Runnable{
Zhuan z;
public ZhuanCang(Zhuan z) {
this.z = z;
}
@Override
public void run() {
while (true) {
synchronized (z) {
while (z.num % 3 != 0) {//所以改用while语句,当被唤醒后如果抢到锁还是会进行判断
//if (z.num % 3 != 1){如果用if结构,当线程被唤醒是直接往后执行,不能达到预期效果
try {
z.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
// }
}
System.out.println("造砖中");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("砖造好了,搬砖工快来搬");
z.num++;
z.notifyAll();
}
}
}
}
搬砖工:
public class BanZhuanGong implements Runnable{
Zhuan z;
public BanZhuanGong(Zhuan z) {
this.z = z;
}
@Override
public void run() {
while (true) {
synchronized (z){
while (z.num % 3 != 1) {//所以改用while语句,当被唤醒后如果抢到锁还是会进行判断
//if (z.num % 3 != 1){如果用if结构,当线程被唤醒是直接往后执行,不能达到预期效果
try {
z.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
// }
}
z.notifyAll();
System.out.println("搬砖,搬砖,搬砖");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("砖搬完了,休息");
z.num++;
}
}
}
}
建筑师:
public class JianZuShi implements Runnable {
Zhuan z;
public JianZuShi(Zhuan z) {
this.z = z;
}
@Override
public void run() {
while (true) {
synchronized (z) {
while (z.num % 3 != 2){//所以改用while语句,当被唤醒后如果抢到锁还是会进行判断
//if (z.num % 3 != 1){如果用if结构,当线程被唤醒是直接往后执行,不能达到预期效果
try {
z.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
// }
}
System.out.println("建房子中");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("没砖了,休息,等搬砖工去搬砖");
z.num++;
z.notifyAll();
}
}
}
}
测试类:
public class Test {
public static void main(String[] args) {
Zhuan z = new Zhuan();
ZhuanCang zc = new ZhuanCang(z);
BanZhuanGong bzg = new BanZhuanGong(z);
JianZuShi jzs = new JianZuShi(z);
Thread t1 = new Thread(zc);
Thread t2 = new Thread(bzg);
Thread t3 = new Thread(jzs);
t1.start();
t2.start();
t3.start();
}
}
运行结果:
造砖中
砖造好了,搬砖工快来搬
搬砖,搬砖,搬砖
砖搬完了,休息
建房子中
没砖了,休息,等搬砖工去搬砖
造砖中
砖造好了,搬砖工快来搬
搬砖,搬砖,搬砖
砖搬完了,休息
建房子中
没砖了,休息,等搬砖工去搬砖
总结:
1.调用wait()和notfiy()以及notifyAll()方法的锁对象要一致
2.线程的调度是抢占式调度
3.当线程进入了无限等待,那么该线程就不会抢占锁对象,和cpu资源
4.线程如果从无限等待状态被唤醒了,那么当抢到锁对象,就会继续从进入无限等待的位置继续往下执行