LockSupport 线程工具类有啥用?
Posted Java技术栈
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LockSupport 线程工具类有啥用?相关的知识,希望对你有一定的参考价值。
点击关注公众号,Java干货及时送达
本文转自公众号:程序猿阿星
内容大纲
LockSupport基本概念
LockSupport
是线程工具类,主要作用是阻塞和唤醒线程,底层实现依赖Unsafe
,同时它还是锁和其他同步类实现的基础,LockSupport
提供两类静态函数分别是park
和unpark
,即阻塞与唤醒线程,下面是两段代码示例
示例-1
public static void main(String[] agrs) throws InterruptedException {
Thread th = new Thread(() -> {
//阻塞当前线程
LockSupport.park();
System.out.println("子线程执行---------");
});
th.start();
//睡眠2秒
Thread.sleep(2000);
System.out.println("主线程执行---------");
//唤醒线程
LockSupport.unpark(th);
}
}
输出结果:
主线程执行---------
子线程执行---------
上述示例中,子线程th
调用LockSupport.park()
阻塞,主线程睡眠2
秒后,执行LockSupport.unpark(th)
唤醒th
线程,先阻塞后唤醒非常好理解,接下来读者们再看下面的示例
示例-2
public static void main(String[] agrs) throws InterruptedException {
Thread th = new Thread(() -> {
//唤醒当前线程
LockSupport.unpark(Thread.currentThread());
//阻塞当前线程
LockSupport.park();
System.out.println("子线程执行---------");
});
th.start();
//睡眠2秒
Thread.sleep(2000);
System.out.println("主线程执行---------");
}
输出结果:
子线程执行---------
主线程执行---------
嗯?先唤醒th
线程,再阻塞th
线程,最终th
线程没有被阻塞,这是为什么?下面LockSupport
的设计思路会为读者们解开疑惑,并更进一步明确是park
和unpark
的语义(从广义上来说park
和unpark
代表阻塞和唤醒)。
设计思路
LockSupport
的设计思路是通过许可证来实现的,就像汽车上高速公路,入口处要获取通行卡,出口处要交出通行卡,如果没有通行卡你就无法出站,当然你可以选择补一张通行卡。
LockSupport
会为使用它的线程关联一个许可证(permit
)状态,permit
的语义「是否拥有许可」,0
代表否,1
代表是,默认是0
。
LockSupport.unpark
:指定线程关联的permit
直接更新为1
,如果更新前的permit<1
,唤醒指定线程LockSupport.park
:当前线程关联的permit
如果>0
,直接把permit
更新为0
,否则阻塞当前线程
线程
A
执行LockSupport.park
,发现permit
为0
,未持有许可证,阻塞线程A
线程
B
执行LockSupport.unpark
(入参线程A
),为A
线程设置许可证,permit
更新为1
,唤醒线程A
线程
B
流程结束线程
A
被唤醒,发现permit
为1
,消费许可证,permit
更新为0
线程
A
执行临界区线程
A
流程结束
经过上面的分析得出结论unpark
的语义明确为「使线程持有许可证」,park
的语义明确为「消费线程持有的许可」,所以unpark
与park
的执行顺序没有强制要求,只要控制好使用的线程即可,unpark=>park
执行流程如下
permit
默认是0
,线程A
执行LockSupport.unpark
,permit
更新为1
,线程A
持有许可证线程
A
执行LockSupport.park
,此时permit
是1
,消费许可证,permit
更新为0
执行临界区
流程结束
最后再补充下park
注意点,因park
阻塞的线程不仅仅会被unpark
唤醒,还可能会被线程中断(Thread.interrupt
)唤醒,而且不会抛出InterruptedException
异常,所以建议在park
后自行判断线程中断状态,来做对应的业务处理。
优点
为什么推荐使用LockSupport
来做线程的阻塞与唤醒(线程间协同工作),因为它具备如下优点
以线程为操作对象更符合阻塞线程的直观语义
操作更精准,可以准确地唤醒某一个线程(
notify
随机唤醒一个线程,notifyAll
唤醒所有等待的线程)无需竞争锁对象(以线程作为操作对象),不会因竞争锁对象产生死锁问题
unpark
与park
没有严格的执行顺序,不会因执行顺序引起死锁问题,比如「Thread.suspend
和Thread.resume
」没按照严格顺序执行,就会产生死锁
另外LockSupport
还提供了park
的重载函数,提升灵活性
void parkNanos(long nanos)
:增加了超时机制void parkUntil(long deadline)
:加入超时机制(指定到某个时间点,1970
年到指定时间点的毫秒数)void park(Object blocker)
:设置blocker
对象,当线程没有许可证被阻塞时,该对象会被记录到该线程的内部,方便后续使用诊断工具进行问题排查void parkNanos(Object blocker, long nanos)
:设置blocker
对象,加入超时机制void parkUntil(Object blocker, long deadline)
:设置blocker
对象,加入超时机制(指定到某个时间点,1970
年到指定时间点的毫秒数)
建议使用时,传入blocker
对象,至于超时根据业务场景选择
实践
使用LockSupport
来完成一道阿里经典的多线程协同工作面试题。
有3
个独立的线程,一个只会输出A
,一个只会输出B
,一个只会输出C
,在三个线程启动的情况下,请用合理的方式让他们按顺序打印ABCABC
。
思路如下
准备
3
个线程,分别固定打印A、B、C
线程输出完
A、B、C
后需要阻塞等待唤醒额外准备第
4
个线程,作为另外3
个线程的调度器,有序的控制3
个线程执行
是不是很简单,下面通过代码来实践
public static void main(String[] agrs) throws InterruptedException {
LockSupportMain lockSupportMain = new LockSupportMain();
//定义线程t1、t2、t3执行的函数方法
Consumer<String> consumer = str -> {
while (true) {
//线程消费许可证,并传入blocker,方便后续排查问题
LockSupport.park(lockSupportMain);
//防止线程是因中断操作唤醒
if (Thread.currentThread().isInterrupted()){
throw new RuntimeException("线程被中断,异常结束");
}
System.out.println(Thread.currentThread().getName() + ":" + str);
}
};
/**
* 定义分别输出A、B、C的线程
*/
Thread t1 = new Thread(() -> {
consumer.accept("A");
},"T1");
Thread t2 = new Thread(() -> {
consumer.accept("B");
},"T2");
Thread t3 = new Thread(() -> {
consumer.accept("C");
},"T3");
/**
* 定义调度线程
*/
Thread dispatch = new Thread(() -> {
int i=0;
try {
while (true) {
if((i%3)==0) {
//线程t1设置许可证,并唤醒线程t1
LockSupport.unpark(t1);
}else if((i%3)==1) {
//线程t2设置许可证,并唤醒线程t2
LockSupport.unpark(t2);
}else {
//线程t3设置许可证,并唤醒线程t3
LockSupport.unpark(t3);
}
i++;
TimeUnit.MILLISECONDS.sleep(500);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
//启动相关线程
t1.start();
t2.start();
t3.start();
dispatch.start();
}
输出内容:
T1:A
T2:B
T3:C
T1:A
T2:B
T3:C
T1:A
T2:B
T3:C
最后再留个题目给读者们思考,使用包含但不限于Synchronized
、ReentrantLock
来完成这个功能。另外,关注公众号Java技术栈,在后台回复:面试,可以获取我整理的 Java 多线程系列面试题和答案,非常齐全。
关注Java技术栈看更多干货
获取 Spring Boot 实战笔记!
以上是关于LockSupport 线程工具类有啥用?的主要内容,如果未能解决你的问题,请参考以下文章