并发2️⃣Java线程②wait/notify、park/unpark、状态转换
1、wait/notify
背景知识:synchronized、Monitor
1.1、API 介绍
- Object 类提供的方法。
- 必须成为对象 Monitor 的
Owner
,才能调用这两个方法。
1.1.1、wait()
使 Monitor 的
Owner
进入WaitSet
-
wait(long):最长等待指定毫秒数。
-
wait(long, int):最长等待指定毫秒数 + 1。
-
wait():无限期等待。
public final native void wait(long timeout) throws InterruptedException; public final void wait(long timeout, int nanos) throws InterruptedException { if (timeout < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (nanos < 0 || nanos > 999999) { throw new IllegalArgumentException( "nanosecond timeout value out of range"); } if (nanos > 0) { timeout++; } wait(timeout); } public final void wait() throws InterruptedException { wait(0); }
1.1.2、notify()
唤醒
WaitSet
中的等待线程。
-
notify():随机唤醒一个。
-
notifyAll():唤醒所有。
public final native void notify(); public final native void notifyAll();
1.2、原理
-
Monitor 的
Owner
的执行条件不满足时,调用 wait()- 主动释放锁。
- 进入
WaitSet
,变成WAITING
状态。
-
此时其它线程可以成为竞争锁,成为新的
Owner
。 -
新的
Owner
调用 notify() 或 notifyAll() 时,唤醒WaitSet
中的线程。
Waitset 和 EntryList 的区别
Waitset | EntryList | |
---|---|---|
线程状态 | WAITING | BLOCKED |
进入条件 | 已获得锁的线程,由于条件不满足而调用 wait() |
尝试获得锁时,由于锁已被其它线程获取而阻塞 |
唤醒时机 | Owner 调用 notify() 或 notifyAll() |
Owner 释放锁 |
注意 | 唤醒后不会直接成为 Owner,而是进入 EntryList 竞争锁 | 若有多个等待线程,则竞争锁(非公平) |
2、park/unpark
2.1、API 介绍
JUC 包下 LockSupport 提供的方法。
// “暂停”当前线程
public static void park();
// “恢复”某个线程
public static void unpark(Thread thread);
2.1.1、park()
调用 park() 时,判断当前线程是否拥有许可。
- 若线程拥有许可,则消耗许可,不会禁用线程。
- 若线程没有许可,则禁用当前线程。
何时取消禁用?
- 其它线程调用
unpark()
,当前线程解除阻塞。 - 其它线程调用
interrupt()
,中断当前线程。
2.1.2、unpark()
调用 unpark(t),为指定线程 t 添加许可。
- 若线程 t 由于
park()
而阻塞,则解除线程 t 的阻塞。 - 若线程 t 正在运行,则添加许可,线程 t 下次调用
park()
时将不会阻塞。 - 若线程 t 未启动,此方法未必生效。
2.2、原理
每个线程独有一个 Parker 对象,由三部分组成
_counter
:计数器,取值 0/1。_cond
:条件变量,当满足条件时解除阻塞。-mutex
调用 park() 时:判断
_counter
的值
- 若线程拥有许可(值为 1),则消耗许可(值重置为 0),不会禁用线程。
- 若线程没有许可(值为 0),则禁用当前线程,并进入
_cond
阻塞。- 其它线程调用
unpark()
,当前线程解除阻塞。 - 其它线程调用
interrupt()
,中断当前线程。
- 其它线程调用
调用 unpark(t) 时:为指定线程 t 添加许可(
_counter
设为 1)
- 若线程 t 由于
park()
而阻塞(处于_cond
),则设置_counter=1
,解除线程 t 的阻塞并重置counter=0
。 - 若线程 t 正在运行,则添加许可(
counter=1
),线程 t 下次调用park()
时将不会阻塞。
2.3、方法对比
sleep() | wait() | park() | |
---|---|---|---|
API | Thread | Object | LockSupport |
synchronized? | 不需要强制配合 synchronized 使用 | 只能在 synchronized 代码块中使用 | 不需要强制配合 synchronized 使用 |
释放对象锁? | ❌ | ✔ | ❌ |
3、线程状态转换
3.1、NEW → RUNNABLE
- 实例化线程对象时:线程状态为
NEW
。 - 调用
start()
时:线程状态NEW
→RUNNABLE
3.2、RUNNABLE <--> 阻塞
阻塞状态细分
BLOCKED
:等待 Monitor 锁。WAITING
:无限期等待另一个线程执行完某个特定操作。TIMED_WAITING
:在指定等待时间内,等待另一个线程执行完某个特定操作。
3.2.1、WAITING
不会被 CPU 分配时间片,无限期等待显式唤醒。
- join()
- wait()
- park()
join()
- 线程 t1 调用
t1.join()
:t1 进入 t2 对象的 Monitor 的 WaitSet,t1 状态RUNNABLE
→WAITING
。 - 唤醒:其它线程调用
t1.interrupt()
、t2 运行结束:t1 退出 WaitSet,状态WAITING
→RUNNABLE
。
wait()
线程 t 执行到 synchronized(obj){}
获取对象锁,成为 Monitor 的 Owner 后:
- 线程 t 调用
obj.wait()
:t 进入 WaitSet,状态RUNNABLE
→WAITING
。 - 唤醒:其它线程调用
obj.notify()
、obj.notifyAll()
、t.interrupt()
:t 退出 WaitSet,进入 EntryList 竞争锁。- 竞争锁成功:
WAITING
→RUNNABLE
- 竞争锁失败:
WAITING
→BLOCKED
- 竞争锁成功:
park()
- 线程 t 调用
LockSupport.park()
:t 状态RUNNABLE
→WAITING
- 唤醒:其它线程调用
LockSupport.unpark()
、t.interrupt()
:t 状态WAITING
→RUNNABLE
3.2.2、TIMED_WAITING
不会被 CPU 分配时间片,等待显式唤醒。
相比 WAITING,等待超时后自动唤醒。
- sleep(long)
- 对应 join()、wait()、park() 的超时等待方法。
sleep(long)
- 线程 t 调用
Thread.sleep(long)
,状态RUNNABLE
→TIMED_WAITING
- 唤醒:
t1.interrupt()
显式唤醒、等待超时自动唤醒,状态TIMED_WAITING
→RUNNABLE
join(long)
- 线程 t1 调用
t1.join(long)
:t1 进入 t2 对象的 Monitor 的 WaitSet,t1 状态RUNNABLE
→TIMED_WAITING
注意是当前线程在t 线程对象的监视器上等待 - 唤醒:状态
TIMED_WAITING
→RUNNABLE
- 显式唤醒:其他线程调用
t1.interrupt()
、t2 运行结束 - 自动唤醒:t1 等待超时
- 显式唤醒:其他线程调用
wait(long)
线程 t 执行到 synchronized(obj){}
获取对象锁,成为 Monitor 的 Owner 后:
- 线程 t 调用
obj.wait(long)
:t 进入 WaitSet,状态RUNNABLE
→TIMED_WAITING
- 唤醒:t 退出 WaitSet,进入 EntryList 竞争锁。
- 类型
- 显式唤醒:其它线程调用
obj.notify()
、obj.notifyAll()
、t.interrupt()
- 自动唤醒:线程 t 等待超时。
- 显式唤醒:其它线程调用
- 竞争锁
- 竞争锁成功:
TIMED_WAITING
→RUNNABLE
- 竞争锁失败:
TIMED_WAITING
→BLOCKED
- 竞争锁成功:
- 类型
parkXxx(long)
- 线程 t 调用
LockSupport.parkNanos(long)
、LockSupport.parkUntil(long)
:t 状态RUNNABLE
→TIMED_WAITING
- 唤醒:t 状态
TIMED_WAITING
→RUNNABLE
- 显式唤醒:其它线程调用
LockSupport.unpark()
、t.interrupt()
- 自动唤醒:线程 t 等待超时。
- 显式唤醒:其它线程调用
3.2.3、BLOCKED
线程 t 竞争锁失败,状态 RUNNABLE → BLOCKED
- t 进入
synchronized(obj){...}
竞争锁失败:进入 EntryList,状态RUNNABLE
→BLOCKED
- Monitor 的 Owner 执行结束,唤醒 EntryList 中所有阻塞线程进行锁竞争。
- 竞争成功:
BLOCKED
→RUNNABLE
- 竞争失败:
BLOCKED
→RUNNABLE
→BLOCKED
- 竞争成功:
3.3、RUNNABLE → TERMINATED
线程 t 代码执行结束:RUNNABLE
→ TERMINATED