简单介绍
LockSupport 是用来创建锁和其他同步类的基本线程阻塞原语,是线程等待唤醒机制的一种实现工具类。
等待唤醒机制
等待唤醒机制 是线程中的一种协作机制。多线程之间不单有竞争锁的情况,还有相互协作的场景。比如线程A执行完某一操作需要挂起一段时间,将运行的机会让给线程B,当线程B执行完任务后就唤醒线程A继续做任务。
Java 提供了 3 种等待唤醒机制:
方式 | 等待 | 唤醒 | 执行要求 |
---|---|---|---|
Object | wait 方法 | notify 方法(随机唤醒) | ①需要获取锁,否则会报错 ②需先调用 wait 才能调用 notify,否则线程会一直等待 |
Condition | await 方法 | signal 方法(随机唤醒) | ①需要获取锁,否则会报错 ②需先调用 await 才能调用 signal,否则线程会一直等待 |
LockSupport | park 方法 | unpark 方法(指定唤醒) | 无 |
其中,LockSupport 类提供的都是静态的方法且对执行没有要求,这让线程可以在任意位置实现等待或唤醒,非常方便。
实现原理
LockSupport 类使用 Permit(许可) 概念来做到等待和唤醒线程的功能:
1 | 每个使用 LockSupport 的线程都有一个 Permit,它相当于通行开关(0:等待 1: 通行),默认值为 0,最大值为 1。 |
当线程调用 park()
方法时:
- 有许可,即 Permit 值为 1 时,会将 Permit 改成 0,同时正常退出方法。
- 无许可,即 Permit 值为 0 时,线程会被挂起(线程状态变成 WAIT)。
当线程调用 unpark(thread)
方法时:
- 指定线程的 Permit 值会变成 1(多次
unpark()
,Permit 值也是 1),如果指定的线程处在 WAIT 状态,则会被唤醒。
线程调用 park()
方法是否被挂起只与它的许可值相关,因此,如果其他线程先调用 unpark(t1)
方法,t1 线程再调用 park()
是不会被挂起的。
案例演示
- 案例1
1 | public class LockSupportTest { |
运行结果:
1 | t1 开始等待 |
从结果可以看出,t1 线程先调用 park()
方法后被挂起等待,1 秒后主线程调用 unpark(t1)
,最后 t1 被唤醒执行 System.out
输出后结束线程。
- 案例2
1 | public class LockSupportTest { |
运行结果:
1 | 2023-02-21T10:47:11.774 -> main 提前开始唤醒 t1 |
主线程先调用 unpark(t1)
方法,1 秒后 t1 线程再调用 park()
并没有被挂起等待,而是随后立即执行了 System.out 的输出结束线程。
小结
- LockSupport 通过 Permit 实现等待唤醒机制。
- LockSupport 通过
park()
挂起线程(Permit 值变成 0),通过unpark(thread)
唤醒线程(Permit 值变成 1)。 - 使用 LockSupport 的线程都有一个 Permit 且最大值为 1,即无论调用多少次
unpark(thread)
,最终 Permit 值都是 1。