1、什么是线程通讯
由于线程之间是抢占式执行的,因此线程之间执行的先后顺序难以预知,但实际开发中有时候我们希望合理地协调多个线程之间的执行先后顺序。
2、方法介绍
2.1 wait()/wait(long timeout):让当前线程进入等待状态。
wait 执行流程:
①使当前的线程进入休眠状态
②释放当前占有的锁
③满足一定条件时被唤醒,尝试重新获取锁。
wait 结束等待的条件:
①其他线程调用该对象的notify方法
②wait (long timeout)等待时间超时,即当线程超过了设置的时间,自动恢复执行
③其他线程调用该等待线程的interrupted 方法,导致wait 抛出异常
2.2 notify() :唤醒当前对象上的一个休眠的线程。(随机)
synchronized(lock){
//发出唤醒通知
lock.notify(); //必须是同一个对象
System.out.println("线程4:执行了唤醒操作")
}
notify 调用之后并不是立即唤醒线程开始执行,而是要等待notify中的 synchronized 执行完(释放锁)之后才能真正地被唤醒起来开始执行。若有多个线程等待,则由线程调度器随机挑选一个呈wait状态的线程。
2.3 notifyAll() : 唤醒当前对象上的所有线程。
使用notifyAll()方法可以一次性唤醒所有的等待线程
※ notify 都是需要另一个线程来进行操作唤醒,实现了线程之间的通讯。这三个方法都必须配合synchronized 一起使用,notifyAll 并不是唤醒所有wait的对象,而是唤醒当前对象所有的wait的线程。
3、注意事项
①wait / notify /notifyAll 必须要配合 synchronized 一起执行。 (潜理解:JVM底层规定,如若不这样,运行时会报错)
深层次原因如下:
②wait / notify / notifyAll 进行 synchronized 加锁,一定要使用同一个对象进行加锁。
③当调用了 notify / notifyAll 之后不是立即执行唤醒操作而是待此加锁代码块执行完毕释放锁之后再进行线程的唤醒操作。
④ notifyAll 并不是唤醒所有的wait 等待的线程,而是唤醒当前对象处于wait 等待的所有线程。
4、相关比较
wait() VS wait (long timeout)
不同点:
①有参的wait 当线程超过了设置的时间后,自动恢复执行;而无参wait 会无限等待下去,
wait(0)也表示无限等待状态。
②使用无参wait方法,线程会进入WAITING;使用有参wait,线程会进入TIMED_WAITING。
相同点:
①无论是有参还是无参,他都可以使当前线程进入休眠状态。
②两者都可以使用notify / notifyAll 进行唤醒。
wait(0) VS sleep(0)
wait(0):无限期等待下去
sleep(0):相当于Thread.yield(),让出CPU执行权,重新调度,但是sleep(0)会继续执行
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 个线程,先让线程休眠 1s 之后,尝试获取,看能不能获取到锁
// 如果可以获取到锁,说明休眠时线程是释放锁的,而如果获取不到锁,说明是不释放锁
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(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("尝试获取 sleep 方法的锁");
synchronized (lock2) {
System.out.println("成功获取 sleep 的锁");
}
}, "sleep2");
t4.start();
}
}
总结
wait VS sleep
相同点:
①都是可以让线程进入休眠状态。
②都可以响应Interrupt(中断)请求。
不同点:
①wait 必须配合 synchronized 一起使用;而sleep 不需要(属于线程的方法)。
②wait 属于 Object(对象)的方法;而sleep属于Thread(线程)的方法。
③sleep 不释放锁;而wait释放锁
④sleep 必须传递一个数值类型的参数,而wait 可以不传参。
⑤sleep 让线程进入到TIMED_WAITING 状态;而无参的wait 方法让线程进入WAITING状态。
⑥一般情况下sleep 只能等待超时时间过了之后再恢复执行;而wait 可以接收唤醒指令,进行执行。
线程休眠补充:LockSupport park / unpark
其是对wait的补充,解决了wait 不能指定唤醒的缺陷,有以下特点:
①使用时不需要加锁
②不会抛出Interrupt 的异常
③可以指定线程唤醒。