LockSupport
线程阻塞工具类(LockSupport):所有方法都是静态方法,可以让线程在任意位置阻塞和唤醒。
LockSupport类使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能,可以把许可看成是一种(0,1)信号量(Semaphore),但与Semaphore不同的是,许可的累加上限是1.
初始时,permit为0,当调用unpark方法的时候,线程的permit加1,当调用park方法的时候,如果permit为0,则调用线程进入阻塞状态。
简单来说:park是等待一个许可,unpark是为某线程提供一个许可
- 如果线程A调用park,那么除非另一个线程调用unpark(A)给A一个许可,否则线程A将阻塞在park操作上。
区别于wait/notify
- wait和notify都是Object中的方法,在调用这两个方法前必须先获得锁对象,但是park不需要获取某个对象的锁就可以锁住线程。
- notify只能随机选择一个线程唤醒,无法唤醒指定的线程,unpark可以唤醒一个指定的线程。
注:
- unpark不会对wait起作用,notify也不会对park起作用。
- park在中断或唤醒后,不会像sleep一样去清除interrupt标志位,也不清楚自身是否是由于中断被唤醒,所以需要自己手动去查看是否被中断标记
Thread.interrupted()
。
需要特别注意的是:LockSupport底层使用Posix线程库pthreads的系统级别锁互斥量mutex和condition,所以它的系统消耗是非常大的,尽量降低使用它的频率。
如:在AQS类中会再三确认是否无法获得锁,当确实无法获取锁的时候,那么park等待被唤醒后再去抢锁。
源码分析
Java底层每个线程都会有一个Parker实例与之对应,Parker实例主要来维护_counter变量。
- _counter = 0:没有permit(许可),而park操作的目的就是等待直到 _counter从1设置为0成功。
- _counter = 1:有一个parmit(许可),而unpark的目的就是设置 _counter为1(调用多次也是1而不会累加)。
park底层原理流程图
具体底层源码:UNSAFE.park
1 | void Parker::park(bool isAbsolute, jlong time) { |
unpark底层原理流程图
具体底层源码:UNSAFE.unpark
1 | void Parker::unpark() { |
LockSupport类
1 | // Hotspot implementation via intrinsics API |
阻塞
park()
1 | // 在许可可用之前阻塞当前线程 |
park(Object blocker)
1 | // blocker:导致线程暂停的同步对象(既线程被谁阻塞的),用于线程监控和分析工具来定位原因。 |
parkUntil(long deadline)
1 | // 和park()方法类似,不过增加了等待的绝对时间 |
parkUntil(Object blocker, long deadline)
1 | // 将当前线程阻塞绝对时间的deadline秒,并且将当前线程的parkBlockerOffset设置为blocker |
parkNanos(long nanos)
1 | // 和park()方法类似,不过增加了等待的相对时间 |
parkNanos(Object blocker, long nanos)
1 | // 阻塞当前线程nanos秒 |
唤醒
unpark
1 | // 如果给定线程的许可尚不可用,则使其可用 |