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

2022-11-30 15:19:33 浏览数 (1)

要想实现多个线程之间的协同,如:线程执行先后顺序、获取某个线程执行的结果等。 涉及到线程之间相互通信,分为如下四类:

1 文件共享

2 网络共享

socket编程

3 共享变量

4 线程协作(JDK API)

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

JDK中对于需要多线程协作完成某一任务的场景,提供了对应API支持。 多线程协作的典型场景是:生产者-消费者模型。(线程阻塞、 线程唤醒)

示例

  • 线程-1去买包子,没有包子,则不再执行
  • 线程-2生产出包子,通知线程-1继续执行

4.1 suspend、resume(废弃)

  • 调用suspend挂起目标线程
  • resume恢复线程执行

但该组合很容易写出

死锁

  • 同步代码中使用
  • 先后顺序:suspend比resume后执行

所以用如下机制替代

4.2 wait/notify

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

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

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

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

正常使用

死锁

synchronized 或 lock

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

park/unpark

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

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

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

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

正常

死锁

5 伪唤醒

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

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

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

0 人点赞