JavaWeb12-线程通讯(线程等待和唤醒)
目录
1.方法介绍
1.1.wait()/wait(long timeout):让当前线程进入等待状态。
1.1.1.wait执行流程
1.1.2.wait结束等待的条件
1.1.3.wait() VS wait(long timeout)
1.1.4.为什么wait要放在Object中?
--->PS:wait(0) 和 sleep(0) 的区别
--->PS:wait 和 sleep 释放锁行为的区别
--->PS:(常见面试题)wait 和 sleep 的区别(小结)
--->PS:3种方法让线程进入休眠/等待状态
1.2.notify():唤醒当前对象上一个休眠的线程(随机)。
1.3.notifyAll():唤醒当前对象上的所有线程。
2.注意事项:
由于线程之间是抢占式执⾏的,因此线程之间执⾏的先后顺序难以预知。但是实际开发中有时候希望合理地协调多个线程之间的执⾏先后顺序。
球场上的每个运动员都是独⽴的 "执⾏流",可以认为是⼀个 "线程"。⽽完成⼀个具体的进攻得分动作,则需要多个运动员相互配合, 按照⼀定的顺序执⾏⼀定的动作,线程1 先 "传球",线程2 才能 "扣篮"。
1.方法介绍
完成这个协调⼯作(线程通讯),主要涉及到以下三个⽅法。注意这三个方法都是对象级别的(需要通过"对象."来调用),【不是锁级别,如Thread.sleep()】都是Object类的内置方法。
1.1.wait()/wait(long timeout):让当前线程进入等待状态。
1.1.1.wait执行流程
- 使当前执⾏代码的线程进⾏等待。(把线程放到等待队列中)。
- 释放当前的锁。
- 满⾜⼀定条件时被唤醒, 重新尝试获取这个锁。
1.1.2.wait结束等待的条件
- 其他线程调⽤该对象的 notify ⽅法。wait/notify 唤醒顺序是无序的。
- wait 等待时间超时 (wait ⽅法提供⼀个带有 timeout 参数的版本, 来指定等待时间)。
- 其他线程调⽤该等待线程的 interrupted ⽅法, 导致 wait 抛出 InterruptedException 异常。

/*** wait使用*/
public class WaitDemo {public static void main(String[] args) {Object lock = new Object();Thread t1 = new Thread(() -> {System.out.println("线程1开始执行");try {synchronized (lock) {System.out.println("线程1调用wait方法....");//无限期等待状态lock.wait();}} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程1执行完成");},"线程1");t1.start();}
}
import java.util.concurrent.TimeUnit;public class WaitSleepDemo7 {public static void main(String[] args) throws InterruptedException {Object lock = new Object();Thread t1 = new Thread(() -> {synchronized (lock){System.out.println("线程1:开始执行");try{lock.wait(0);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程1:结束执行");}System.out.println("线程1:终止执行");},"wait0");t1.start();TimeUnit.SECONDS.sleep(1);System.out.println("执行线程1的终止方法");t1.interrupt();}
}
1.1.3.wait() VS wait(long timeout)
1-不同点:wait(long timeout):当线程超过了设置的时间之后,自动恢复执行;而wait():无限等待状态。(0表示无限等待状态)
2-不同点:wait(long timeout):线程会进入WAITING状态;而wait():线程会进入TIMED_WAITING状态。
import java.time.LocalDateTime;public class WaitDemo4 {public static void main(String[] args) {Object lock = new Object();Object lock2 = new Object();new Thread(() -> {System.out.println("线程1:开始执行");synchronized (lock){try {lock.wait();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程1:执行完成");}},"无参wait线程").start();new Thread(() -> {synchronized (lock2){System.out.println("线程2:开始执行 |" + LocalDateTime.now());try {lock2.wait(60 * 60 * 60 * 1000); //1h} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程2:执行完成 |" + LocalDateTime.now());}},"有参wait线程").start();}
}
3-共同点:无论是wait(long timeout)还是wait(),都会使当前线程进入休眠状态。
4-共同点:无论是wait(long timeout)还是wait(),都能使用notify/notifyAll进行唤醒。
import java.time.LocalDateTime;
import java.util.concurrent.TimeUnit;public class WaitDemo6 {public static void main(String[] args) {Object lock = new Object();new Thread(() -> {System.out.println("线程1:开始执行");synchronized (lock){try {lock.wait();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程1:执行完成");}},"无参wait线程").start();new Thread(() -> {synchronized (lock){System.out.println("线程2:开始执行 |" + LocalDateTime.now());try {lock.wait(60 * 60 * 60 * 1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程2:执行完成 |" + LocalDateTime.now());}},"有参wait线程").start();new Thread(() -> {try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}synchronized (lock){System.out.println("唤醒所有线程");lock.notifyAll();}}).start();}
}
1.1.4.为什么wait要放在Object中?
wait 使用要加锁,也就是要操作锁,锁是针对对象级别的而非线程级别的,线程和对象是⼀对多,所以 wait 最便利的方式是放在 Object 中。
wait(num)和sleep(num):当num>0时,二者执行效果一样。
--->PS:wait(0) 和 sleep(0) 的区别
- wait(0) 表示无期限地等待,直到有线程唤醒它为止。
- Thread.sleep(0) 调用后会让出CPU执行权,让线程稍做休眠,然后重新调度,重新触发一次 CPU 竞争,不管是否竞争到CPU执行权,都会继续执行,直到执行结束。(类似于Thread.yield())
/*** wait(0)和sleep(0)*/ public class WaitSleepDemo7 {public static void main(String[] args) throws InterruptedException {Object lock = new Object();Thread t1 = new Thread(() -> {synchronized (lock){System.out.println("线程1:开始执行");try{lock.wait(0);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程1:执行结束");}},"wait(0)");t1.start();Thread t2 = new Thread(() -> {System.out.println("线程2:开始执行");try {Thread.sleep(0);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程2:执行结束");},"sleep(0)");t2.start();} }
--->PS:wait 和 sleep 释放锁行为的区别
wait和sleep在有锁的情况下,锁的处理行为是完全不同的:
- wait方法(不管是有参还是无参)在执行时都会释放锁。
- sleep方法不会释放锁。
import java.util.concurrent.TimeUnit;/*** wait和sleep释放锁行为的区别*/ public class WaitSleepDemo8 {public static void main(String[] args) throws InterruptedException {Object lock = new Object();Object lock2 = new Object();Thread t1 = new Thread(() -> {synchronized (lock){System.out.println("线程1:开始执行");try{lock.wait(3 * 1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程1:结束执行");}},"wait");t1.start();Thread t2 = new Thread(() -> {synchronized (lock2) {System.out.println("线程2:开始执行");try {Thread.sleep(3 * 1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程2:结束执行");}},"sleep");t2.start();//创建2个线程,先让线程休眠1秒之后,尝试获取锁,看能不能获取到锁//如果可以获取到锁,说明休眠时线程是释放锁的,而如果获取不到锁,说明是不释放锁的Thread t3 = new Thread(() -> {try{TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("尝试获取wait方法的锁");synchronized (lock){System.out.println("成功获取wait的锁");}},"wait2");t3.start();Thread t4 = new Thread(() -> {try{TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("尝试获取sleep方法的锁");synchronized (lock2){System.out.println("成功获取sleep的锁");}},"sleep2");t4.start();} }
为什么wait释放锁而sleep不释放锁?
JVM 强制语法检查,wait 方法默认等待无期限。
--->PS:(常见面试题)wait 和 sleep 的区别(小结)
二者的相同点:
- 都可以让线程休眠。
- 都可以响应 interrupt 中断的请求。
- wait(num) 和 sleep(num):当num>0时,二者执行效果一样。
二者的不同点:
- wait 必须在 synchronized 中使用;而 sleep 却不用。
- wait 是 Object 的方法(属于对象级别);而sleep 是 Thread 的方法(属于线程级别)。
- wait 释放锁;而sleep 不释放锁。
- wait 有可能无限期地等待下去;而sleep 有明确的终止等待时间。 即:wait 可传参,也可不传参;而 sleep必须要传递一个数值类型的参数,否则会报错。
- wait 和 sleep 产生的线程状态是不同的:无参的 wait 是 WAITING 状态;而sleep 是 TIMED_WAITING 状态。
- 一般情况下(响应 interrupt 除过),wait 可以接收一个 notify/notifyAll 之后就继续执行;而 sleep 只能等待超过时间之后再恢复执行。
- wait(0) 表示无期限地等待;而sleep(0)表示重新触发一次 CPU 竞争。
--->PS:3种方法让线程进入休眠/等待状态
- sleep(传参设置休眠时间;不可唤醒)
- TimeUnit(传参设置休眠时间;不可唤醒)
- wait(可传参设置休眠时间,也可不传参无限等待;可以唤醒)
1.2.notify():唤醒当前对象上一个休眠的线程(随机)。
-
⽅法notify()也要在同步⽅法或同步块中调⽤,该⽅法是⽤来通知那些可能等待该对象的对象锁的其它线程,对其发出通知notify,并使它们重新获取该对象的对象锁。
-
如果有多个线程等待,则由线程调度器随机挑选出⼀个呈 wait 状态的线程。(并没有 "先来后到") (官方是随机的,但具体实现针对不同的JVM是不一样的)
/*** notify使用*/
public class WaitDemo2 {public static void main(String[] args) {Object lock = new Object();Object lock2 = new Object();Thread t1 = new Thread(() -> {System.out.println("线程1开始执行");try {synchronized (lock) {System.out.println("线程1调用wait方法....");//无限期的等待状态lock.wait();}} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程1执行完成");},"线程1");Thread t2 = new Thread(() -> {System.out.println("线程2开始执行");try {synchronized (lock) {System.out.println("线程2调用wait方法....");//无限期的等待状态lock.wait();}} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程2执行完成");},"线程2");Thread t3 = new Thread(() -> {System.out.println("线程3开始执行");try {synchronized (lock2) {System.out.println("线程3调用wait方法....");//无限期的等待状态lock2.wait();}} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程3执行完成");},"线程3");t1.start();t2.start();t3.start();//唤醒lock对象上休眠的线程(随机唤醒一个)Thread t4 = new Thread(() -> {try {Thread.sleep(1500);} catch (InterruptedException e) {}System.out.println("线程4:开始执行,唤醒线程");synchronized (lock) {//发出唤醒通知lock.notify();System.out.println("线程4:执行了唤醒操作");try{Thread.sleep(2000);} catch (InterruptedException e) {}System.out.println("线程4:synchronized执行完了");}}, "线程4");t4.start();}
}
1.3.notifyAll():唤醒当前对象上的所有线程。
/*** notifyAll使用*/
public class WaitDemo3 {public static void main(String[] args) {Object lock = new Object();Object lock2 = new Object();Thread t1 = new Thread(() -> {System.out.println("线程1开始执行");try {synchronized (lock) {System.out.println("线程1调用wait方法....");//无限期的等待状态lock.wait();System.out.println("线程1:恢复执行之后又进入休眠状态");Thread.sleep(2000);System.out.println("线程1执行完成");}} catch (InterruptedException e) {e.printStackTrace();}},"线程1");Thread t2 = new Thread(() -> {System.out.println("线程2开始执行");try {synchronized (lock2) {System.out.println("线程2调用wait方法....");//无限期的等待状态lock2.wait();System.out.println("线程2:恢复执行之后又进入休眠状态");Thread.sleep(2000);System.out.println("线程2执行完成");}} catch (InterruptedException e) {e.printStackTrace();}},"线程2");Thread t3 = new Thread(() -> {System.out.println("线程3开始执行");try {synchronized (lock) {System.out.println("线程3调用wait方法....");//无限期的等待状态lock.wait();System.out.println("线程3:恢复执行之后又进入休眠状态");Thread.sleep(2000);System.out.println("线程3执行完成");}} catch (InterruptedException e) {e.printStackTrace();}},"线程3");t1.start();t2.start();t3.start();//唤醒lock对象上休眠的线程(随机唤醒一个)Thread t4 = new Thread(() -> {try {Thread.sleep(500);} catch (InterruptedException e) {}System.out.println("线程4:开始执行,唤醒线程");synchronized (lock){//发出唤醒通知lock.notifyAll();System.out.println("线程4:执行了唤醒操作");try{Thread.sleep(2000);} catch (InterruptedException e) {}System.out.println("线程4:synchronized执行完了");}}, "线程4");t4.start();}
}
2.注意事项:
①wait/notify/notifyAll必须要配合synchronized一起使用,否则会报错(JVM的强制规定,之所以这样规定,是为了解决线程通讯时执行混乱的问题,synchronized起到约束限制的作用)。
②wait/notify/notifyAll进行synchronized加锁,一定要使用同一个对象进行加锁。
③当调用了notify/notifyAll之后,当前线程不会⻢上释放该对象锁,要等到执⾏notify/notifyAll⽅法的线程将程序执⾏完,也就是退出同步代码块(synchronized)之后才会释放对象锁。【程序也并不会立即恢复执行,而是尝试获取锁,只有得到锁之后才能继续执行。】
④notify/notifyAll可以多次调用,也可以在wait之前调用(是无用功)。
⑤notifyAll 并不是唤醒所有 wait 等待的线程,而是唤醒当前对象处于 wait 等待的所有线程。