LockSupport 详解
2024-10-28 08:52:07
# Technical
# JavaConcurrency
为什么 LockSupport 是核心基础类?
写出分别通过 wait/notify 和 LockSupport 的 park/unpark 实现同步?
LockSupport.park() 会释放锁资源吗?那么 Condition.await() 呢?
Thread.sleep()、Object.wait()、Condition.await()、LockSupport.park() 的区别?
如果在 wait() 之前执行了 notify() 会怎样?
如果在 park() 之前执行了 unpark() 会怎样?
LockSupport 用于提供阻塞和唤醒线程的功能。它允许线程在不使用传统的同步机制(如synchronized和ReentrantLock)的情况下被挂起和恢复,适用于实现更复杂的线程控制和等待机制
使用
利用 LockSupport 实现 N 个线程依次打印 1-100
1 | private static volatile int count = 1; |
这里不加 continue 会导致?…
属性
1 | public class LockSupport { |
构造函数
1 | private LockSupport() {} |
LockSupport 只有一个私有的构造函数,说明其无法被实例化
核心方法
1 | // 一直等待,直到获得许可 |
还有个指定 blocker 的方法
1 | public static void park(Object blocker) { |
这个 blocker 参数主要用于诊断目的:
- 帮助识别为什么线程被阻塞
- 在线程转储中提供更多信息
- 便于调试复杂的并发问题
原理与优势
首先,需要理解两个关键概念:
- 许可:可以把它想象成一种令牌,每个线程最多只有一个许可
- 计数器:每个线程都有一个与之关联的计数器,用于记录许可的状态
park 方法原理
当调用 LockSupport.park()
或 LockSupport.park(Object blocker)
时:
- 首先检查许可的计数器,如果计数器大于 0,则立即返回,并将计数器 -1
- 如果计数器为 0,则调用操作系统的方法将当前线程置为等待状态(WAITING/TIMED_WAITING)
- 线程会一直等待,直到以下情况发生之一:a) 其他线程调用了
LockSupport.unpark(Thread)
方法,将该线程作为参数传入;b) 其他线程中断了该线程;c) 等待操作无缘故返回(这种情况被称为「虚假唤醒」,非常罕见)
unpark 方法原理
- 如果指定线程的许可计数器为 0,则将其设置为 1
- 如果指定线程当前因 park 操作而被阻塞,则唤醒该线程
- 如果指定线程当前没有被阻塞,则什么也不做,只是保证下一次 park 操作不会阻塞
优势
与传统的 wait/notify 机制相比,有以下几个优势:
- 不需要在同步块中使用
- park 和 unpark 可以按任意顺序调用,即使 unpark 在 park 之前调用,后续的 park 也不会阻塞
- 使用起来更加简单,不易出错,不会抛出
InterruptedException
异常
注意事项
- park 方法不保证线程一定会阻塞。如果在调用 park 之前或者在 park 阻塞期间,有其他线程调用了 unpark,那么 park 可能会立即返回
- 连续多次调用 unpark 只会累积一个许可