一 简介
1 概述 LockSupport是用于创建锁和其他同步类的阻塞原语。以下是jdk对LockSupport的描述。
Basic thread blocking primitives for creating locks and other synchronization classes.
在《ReentrantLock详解》(地址:https://yq.aliyun.com/articles/460711)中分析源码的时候,我们就已经多次提到使用LockSupport的pack挂起线程,unpack唤醒被挂起的线程,此博客将详述LockSupport的原理以及实现。
2 许可 LockSupport通过许可(permit)实现挂起线程、唤醒挂起线程功能。可以按照以下逻辑理解:
3 pack时 如果线程的permit存在,那么线程不会被挂起,立即返回;如果线程的permit不存在,认为线程缺少permit,所以需要挂起等待permit。
4 unpack时 如果线程的permit不存在,那么释放一个permit。因为有permit了,所以如果线程处于挂起状态,那么此线程会被线程调度器唤醒。如果线程的permit存在,permit也不会累加,看起来想什么事都没做一样。注意这一点和Semaphore是不同的。
二 源码分析
1 park 主要功能:
【java|LockSupport】如果许可存在,那么将这个许可使用掉,并且立即返回。如果许可不存在,那么挂起当前线程,直到以下任意一件事情发生:
- 其他线程以当前线程为参数,调用unpark(thread)方法
- 其他线程通过Thread#interrupt中断当前线程。
- Pack方法没有原因的返回(调用时应该重新检查导致线程暂停的条件)。这一点后面解释。
public static void park(Object blocker) {//获取当前线程Thread t = Thread.currentThread();
//设置线程的blocker对象setBlocker(t, blocker);
//通过UNSAFE调用,挂起线程UNSAFE.park(false, 0L);
//挂起的线程被唤醒以后,需要将阻塞的Blocker清理掉。setBlocker(t, null);
}
2 pack的多版本 LockSupport中pack有多个版本,如下所示:
park(Object) |
挂起当前线程,具体见上面pack的源码分析 |
parkNanos(Object, long) |
指定了一个挂起时间(相对于当前的时间),时间到后自动被唤醒;例如1000纳秒后自动唤醒 |
parkUntil(Object, long) |
指定一个挂起时间(绝对时间),时间到后自动被唤醒;例如2018-02-12 21点整自动被唤醒。 |
park() |
和park(Object)相比少了挂起前为线程设置blocker、被唤醒后清理blocker的操作。 |
parkNanos(long) |
和parkNanos(Object, long)类似,仅少了blocker相关的操作 |
parkUntil(long) |
和parkUntil(Object, long)类似,仅少了blocker相关的操作 |
3 unpark 设置线程许可为可用。
- 如果线程当前已经被pack挂起,那么这个线程将会被唤醒。
- 如果线程当前没有被挂起,那么下次调用pack不会挂起线程。
public static void unpark(Thread thread) {if (thread != null)UNSAFE.unpark(thread);
}
三 分析 1 挂起与阻塞 挂起与阻塞主要的区别应该说是它们面向的对象不同。对线程来说, LockSupport的park/unpark更符合阻塞和唤醒的语义,他们以“线程”作为方法的参数,语义更清晰,使用起来也更方便。而wait/notify使“线程”的阻塞/唤醒对线程本身来说是被动的,要准确的控制哪个线程、什么时候阻塞/唤醒是很困难的,所以是要么随机唤醒一个线程(notify)要么唤醒所有的(notifyAll)。
park和unpark方法不会出现Thread.suspend和Thread.resume的死锁问题。这是因为许可的存在,调用park的线程和另一个试图将其unpark的线程之间的将没有竞争关系。此外,如果线程被中断或者超时,则park将返回。
park方法还可以在其他任何时间“毫无理由”地返回,因此通常必须在重新检查返回条件的循环里调用此方法。从这个意义上说,park 是“忙碌等待”的一种优化,它不会浪费这么多的时间进行自旋,但是必须将它与unpark配对使用才更高效。
2 使用模型 以下伪代码是pack的常用模型。
while ( !enable() ) {//其他业务逻辑......LockSupport.park(this);
//其他业务逻辑......}
以下是ReentrantLock中pack的使用代码
final boolean acquireQueued(final Node node, int arg) {boolean failed = true;
try {boolean interrupted = false;
for (;
;
) {final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {setHead(node);
p.next = null;
// help GCfailed = false;
return interrupted;
}if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())interrupted = true;
}} finally {if (failed)cancelAcquire(node);
}}private final boolean parkAndCheckInterrupt() {LockSupport.park(this);
return Thread.interrupted();
}
推荐阅读
- Java|Java基础——数组
- 人工智能|干货!人体姿态估计与运动预测
- java简介|Java是什么(Java能用来干什么?)
- Java|规范的打印日志
- Linux|109 个实用 shell 脚本
- 程序员|【高级Java架构师系统学习】毕业一年萌新的Java大厂面经,最新整理
- Spring注解驱动第十讲--@Autowired使用
- SqlServer|sql server的UPDLOCK、HOLDLOCK试验
- jvm|【JVM】JVM08(java内存模型解析[JMM])
- 技术|为参加2021年蓝桥杯Java软件开发大学B组细心整理常见基础知识、搜索和常用算法解析例题(持续更新...)