要想实现多个线程之间的协同,如:线程执行先后顺序、获取某个线程执行的结果等。

涉及到线程之间相互通信,分为如下四类:

1 文件共享

Java线程等待、唤醒通信机制详解_Java

2 网络共享

socket编程

3 共享变量

Java线程等待、唤醒通信机制详解_死锁_02

4 线程协作(JDK API)

细分为: suspend/resume 、 wait/notify、 park/unpark

JDK中对于需要多线程协作完成某一任务的场景,提供了对应API支持。

多线程协作的典型场景是:生产者-消费者模型。(线程阻塞、 线程唤醒)

示例


  • 线程-1去买包子,没有包子,则不再执行
  • 线程-2生产出包子,通知线程-1继续执行
    Java线程等待、唤醒通信机制详解_Java_03

4.1 suspend、resume(废弃)


  • 调用suspend挂起目标线程
  • resume恢复线程执行
    Java线程等待、唤醒通信机制详解_Java_04
    但该组合很容易写出

死锁



同步代码中使用
Java线程等待、唤醒通信机制详解_对象锁_05
Java线程等待、唤醒通信机制详解_对象锁_06



先后顺序:suspend比resume后执行
Java线程等待、唤醒通信机制详解_对象锁_07
Java线程等待、唤醒通信机制详解_Java_08
所以用如下机制替代



4.2 wait/notify

这些方法只能由​同一对象锁​的持有者线程调用,即写在同步块里!否则抛IllegalMonitorStateException。

wait​ 方法导致当前线程等待,加入该对象的等待集合中,并且​放弃当前持有的对象锁​。

notify​/​notifyAll​ 方法唤醒​一个​/​所有​正在等待这个对象锁的线程。


虽然wait会自动解锁,但​对顺序有要求​。若在notify被调用后, 才调用wait,则线程会永远处于​WAITING​态。


正常使用

Java线程等待、唤醒通信机制详解_等待状态_09

死锁

Java线程等待、唤醒通信机制详解_Java_10

Java线程等待、唤醒通信机制详解_死锁_11

synchronized 或 lock

线程先要获得并持有锁,必须在锁块(synchronized或lock)中。必须要​先等待后唤醒​,线程才能够被唤醒。

park/unpark

LockSupport用来创建锁和其他同步类的基本线程阻塞原语:


  • 线程调用​​LockSupport.park​​,则等待“许可”
  • 线程调用​​LockSupport.unpark​​,必须把等待获得许可的线程作为参数进行传递,好让此线程继续运行,为指定线程提供“许可(permit)”

不要求park和unpark方法的调用顺序​,无需写在任何同步代码块里。

多次调用unpark之后,再调用park,线程会直接运行,​不会叠加​,累加上限只有 1,即连续多次调用park,第一次会拿到“许可”直接运行,后续调用还是会进入等待。

正常Java线程等待、唤醒通信机制详解_Java_12

死锁

Java线程等待、唤醒通信机制详解_Java_13

5 伪唤醒

之前代码中用if语句来判断,是否进入等待状态,是错误的​。

官方推荐​​应该在循环中检查等待条件​​,因为处于等待状态的线程可能会收到​错误警报和伪唤醒​,如果不在循环中检查等待条件,程序就可能在没有满足结束条件的情况下退出。

伪唤醒是指线程并非因为notify、notifyall、 unpark等API调用而唤醒,而是更底层原因导致的。

Java线程等待、唤醒通信机制详解_Java_14