线程的等待通知机制
- 一:情景再现:
- 二:等待通知机制:
- 2.1 wait()方法
- 2.2 notify()方法
- 2.22:唤醒了t2线程,t1线程仍处于阻塞等待状态
- 2.23 唤醒了t1线程,t2线程仍处于阻塞等待状态
- 2.24:notifyAll()
一:情景再现:
假设有3个滑稽,1号滑稽在ATM中取钱,2,3号滑稽只能在门口阻塞等待,1号滑稽发现ATM中没钱了,就从ATM中出来了,在2号滑稽进去之前,1号滑稽又进去了,2号又只能阻塞等待,1号发现仍然没钱,又出来了,然后再2号进去之前,1号 又进去了,就这样,1号进进出出,其他滑稽只能阻塞等待.
上述问题,就称为"线程饿死":线程调度是随机的,很可能出现某个线程频繁的获取释放锁,由于获取的太快,以至于其他 线程捞不到CPU资源,导致程序的效率降低.
二:等待通知机制:
等待通知机制,就可以解决上述问题.
当2号滑稽进ATM后,存了一部分钱,告诉1号滑稽,一号滑稽就可以去取钱了.
通过条件,判断当前逻辑是否能够执行,如果不能执行,就主动wait(主动进行阻塞),就把执行的机会让给别的线程了,避免该线程进行一些无意义的重试.等后续条件实际成熟了(需要其他线程通知),再让阻塞的线程被唤醒.
2.1 wait()方法
wait()方法,是Object提供的方法,任何一个对象,都有这个方法.
wait()内部做的事情,不仅仅是阻塞等待,还要解锁,解锁的同时,进行等待.解锁之后,其他线程就可以获取到锁对象了
要想解锁,那么首先就必须有锁,所以wait()方法往往是和synchronized关键字搭配使用的.
public class Demo3 {
public static void main(String[] args) throws InterruptedException {
Object object=new Object();
System.out.println("解锁之前");
synchronized (object){
object.wait();
}
System.out.println("解锁之后");
}
}
此时main线程就进入阻塞等待状态,直到其他线程将他唤醒.
通过jconsole也可以看到main线程的状态.
2.2 notify()方法
通过另一个线程,去唤醒阻塞的线程.
public class Demo4 {
public static void main(String[] args) throws InterruptedException {
Object object=new Object();
Thread t1=new Thread(()->{
synchronized (object){
System.out.println("t1 等待之前");
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t1 等待之后");
}
});
Thread t2=new Thread(()->{
Scanner scanner=new Scanner(System.in);
synchronized (object){
System.out.println("t2 通知之前");
scanner.next();
object.notify();
System.out.println("t2 通知之后");
}
});
// Thread.sleep(1000);
t2.start();
t1.start();
}
}
当用户时输入内容之后,此时就会使t2线程调度到CPU上,然后才进一步的执行到notify,notify就会唤醒wait()操作,从而使t1回到RUNNABLE状态参与调度.
当然,把t1唤醒,t1是不能立即就能执行的,t1要重新获取到锁.
由于此时t2还没有释放锁,意味着t1会从WAITING->RUNNABLE->BLOCKED
因为t1 ,t2执行抢占式执行,执行顺序不确定,那么就有可能t2先执行了notify,此时t1还没wait,那么notify就不会有任何效果(也不会抛异常),但是后续t1进入wait()之后,就没有人能够唤醒了.
public class Demo4 {
public static void main(String[] args) throws InterruptedException {
Object object=new Object();
Thread t1=new Thread(()->{
synchronized (object){
System.out.println("t1 等待之前");
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t1 等待之后");
}
});
Thread t2=new Thread(()->{
Scanner scanner=new Scanner(System.in);//借助 scanner 控制阻塞. 用户输入之前, 都是阻塞状态.
synchronized (object){
System.out.println("t2 通知之前");
scanner.next();
object.notify();
System.out.println("t2 通知之后");
}
});
// Thread.sleep(1000);
t2.start();
t1.start();
}
}
上述代码t1线程就一直处于阻塞状态,进程也不会结束了.
如果有多个要唤醒的线程,那么唤醒哪一个是随机的.
那么就需要多个锁对象,不同的线程使用不同的锁对象来wait,唤醒的时候,再根据锁对象去唤醒.
2.22:唤醒了t2线程,t1线程仍处于阻塞等待状态
public class Demo5 {
public static void main(String[] args) throws InterruptedException {
Object object=new Object();
Object object2=new Object();
Thread t1=new Thread(()->{
synchronized (object){
System.out.println("t1 等待之前");
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t1 等待之后");
}
});
Thread t2=new Thread(()->{
synchronized (object2){
System.out.println("t2 等待之前");
try {
object2.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t2 等待之后");
}
});
Thread t3=new Thread(()->{
synchronized (object2){
System.out.println("t3 通知之前");
object2.notify();//唤醒了t2线程,t1线程仍处于阻塞等待状态
System.out.println("t3 通知之后");
}
});
t1.start();
t2.start();
Thread.sleep(1000);
t3.start();
}
}
2.23 唤醒了t1线程,t2线程仍处于阻塞等待状态
public class Demo5 {
public static void main(String[] args) throws InterruptedException {
Object object=new Object();
Object object2=new Object();
Thread t1=new Thread(()->{
synchronized (object){
System.out.println("t1 等待之前");
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t1 等待之后");
}
});
Thread t2=new Thread(()->{
synchronized (object2){
System.out.println("t2 等待之前");
try {
object2.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t2 等待之后");
}
});
Thread t3=new Thread(()->{
synchronized (object){
System.out.println("t3 通知之前");
object.notify();//唤醒了t1线程,t2线程仍处于阻塞等待状态
System.out.println("t3 通知之后");
}
});
t1.start();
t2.start();
Thread.sleep(1000);
t3.start();
}
}
2.24:notifyAll()
唤醒所有等待的线程(锁对象相同).
public class Demo5 {
public static void main(String[] args) throws InterruptedException {
Object object=new Object();
Object object2=new Object();
Thread t1=new Thread(()->{
synchronized (object){
System.out.println("t1 等待之前");
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t1 等待之后");
}
});
Thread t2=new Thread(()->{
synchronized (object){
System.out.println("t2 等待之前");
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t2 等待之后");
}
});
Thread t4=new Thread(()->{
synchronized (object2){
System.out.println("t4 等待之前");
try {
object2.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t4 等待之后");
}
});
Thread t3=new Thread(()->{
synchronized (object){
System.out.println("t3 通知之前");
object.notifyAll();//唤醒所有的因object锁引起的阻塞等待,但t4线程不会被唤醒
System.out.println("t3 通知之后");
}
});
t1.start();
t2.start();
t4.start();
Thread.sleep(1000);
t3.start();
}
}