阻塞和唤醒线程——LockSupport功能简介及原理浅析
在java并发包下各种同步组件的底层实现中,LockSupport的身影处处可见。JDK中的定义为用来创建锁和其他同步类的线程阻塞原语。
*Basic thread blocking primitives for creating locks and other
*synchronization classes.
我们可以使用它来阻塞和唤醒线程,功能和wait,notify有些相似,但是LockSupport比起wait,notify功能更强大,也好用的多。
1.1 使用wait,notify阻塞唤醒线程
- 例子
public class WaitNotifyTest {
private static Object obj = new Object();
public static void main(String[] args) {
new Thread(new WaitThread()).start();
new Thread(new NotifyThread()).start();
}
static class WaitThread implements Runnable {
@Override
public void run() {
synchronized (obj) {
System.out.println("start wait!");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end wait!");
}
}
}
static class NotifyThread implements Runnable {
@Override
public void run() {
synchronized (obj) {
System.out.println("start notify!");
obj.notify();
System.out.println("end notify");
}
}
}
}
- 结果

使用wait,notify来实现等待唤醒功能至少有两个缺点:
- 1.由上面的例子可知,wait和notify都是Object中的方法,在调用这两个方法前必须先获得锁对象,这限制了其使用场合:只能在同步代码块中。
- 2.另一个缺点可能上面的例子不太明显,当对象的等待队列中有多个线程时,notify只能随机选择一个线程唤醒,无法唤醒指定的线程。
而使用LockSupport的话,我们可以在任何场合使线程阻塞,同时也可以指定要唤醒的线程,相当的方便。
1.2 使用LockSupport阻塞唤醒线程
- 例子
public class LockSupportTest {
public static void main(String[] args) {
Thread parkThread = new Thread(new ParkThread());
parkThread.start();
System.out.println("开始线程唤醒");
LockSupport.unpark(parkThread);
System.out.println("结束线程唤醒");
}
static class ParkThread implements Runnable{
@Override
public void run() {
System.out.println("开始线程阻塞");
LockSupport.park();
System.out.println("结束线程阻塞");
}
}
}
- 结果

LockSupport.park();可以用来阻塞当前线程,park是停车的意思,把运行的线程比作行驶的车辆,线程阻塞则相当于汽车停车,相当直观。该方法还有个变体LockSupport.park(Object blocker),指定线程阻塞的对象blocker,该对象主要用来排查问题。方法LockSupport.unpark(Thread thread)用来唤醒线程,因为需要线程作参数,所以可以指定线程进行唤醒。
2. LockSupport的其他特色
2.1 可以先唤醒线程再阻塞线程
在阻塞线程前睡眠1秒中,使唤醒动作先于阻塞发生,看看会发生什么
- 例子
public class LockSupportTest {
public static void main(String[] args) {
Thread parkThread = new Thread(new ParkThread());
parkThread.start();
System.out.println("开始线程唤醒");
LockSupport.unpark(parkThread);
System.out.println("结束线程唤醒");
}
static class ParkThread implements Runnable{
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("开始线程阻塞");
LockSupport.park();
System.out.println("结束线程阻塞");
}
}
}
- 结果

先唤醒指定线程,然后阻塞该线程,但是线程并没有真正被阻塞而是正常执行完后退出了。这是怎么回事?我们试着在改动下代码,先唤醒线程两次,在阻塞线程两次,看看会发生什么。
2.2 先唤醒线程两次再阻塞两次会发生什么
- 例子
public class LockSupportTest {
public static void main(String[] args) {
Thread parkThread = new Thread(new ParkThread());
parkThread.start();
for(int i=0;i<2;i++){
System.out.println("开始线程唤醒");
LockSupport.unpark(parkThread);
System.out.println("结束线程唤醒");
}
}
static class ParkThread implements Runnable{
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int i=0;i<2;i++){
System.out.println("开始线程阻塞");
LockSupport.park();
System.out.println("结束线程阻塞");
}
}
}
}
- 结果

可以看到线程被阻塞导致程序一直无法结束掉。对比上面的例子,我们可以得出一个匪夷所思的结论,先唤醒线程,在阻塞线程,线程不会真的阻塞;但是先唤醒线程两次再阻塞两次时就会导致线程真的阻塞。那么这到底是为什么?
3. LockSupport阻塞和唤醒线程原理浅析
既然是浅析,那就不抠底层细节,只讲关键,细节可能有疏漏和不到位的地方。
每个线程都有Parker实例,如下面的代码所示
class Parker : public os::PlatformParker {
private:
volatile int _counter ;
...
public:
void park(bool isAbsolute, jlong time);
void unpark();
...
}
class PlatformParker : public CHeapObj<mtInternal> {
protected:
pthread_mutex_t _mutex [1] ;
pthread_cond_t _cond [1] ;
...
}
LockSupport就是通过控制变量_counter来对线程阻塞唤醒进行控制的。原理有点类似于信号量机制。
- 当调用
park()方法时,会将_counter置为0,同时判断前值,小于1说明前面被unpark过,则直接退出,否则将使该线程阻塞。 - 当调用
unpark()方法时,会将_counter置为1,同时判断前值,小于1会进行线程唤醒,否则直接退出。
形象的理解,线程阻塞需要消耗凭证(permit),这个凭证最多只有1个。当调用park方法时,如果有凭证,则会直接消耗掉这个凭证然后正常退出;但是如果没有凭证,就必须阻塞等待凭证可用;而unpark则相反,它会增加一个凭证,但凭证最多只能有1个。 - 为什么可以先唤醒线程后阻塞线程?
因为unpark获得了一个凭证,之后调用park因为有凭证消费,故不会阻塞。 - 为什么唤醒两次后阻塞两次会阻塞线程。
因为凭证的数量最多为1,连续调用两次unpark和调用一次unpark效果一样,只会增加一个凭证;而调用两次park却需要消费两个凭证。
4. 总结
LockSupport是JDK中用来实现线程阻塞和唤醒的工具。使用它可以在任何场合使线程阻塞,可以指定任何线程进行唤醒,并且不用担心阻塞和唤醒操作的顺序,但要注意连续多次唤醒的效果和一次唤醒是一样的。
JDK并发包下的锁和其他同步工具的底层实现中大量使用了LockSupport进行线程的阻塞和唤醒,掌握它的用法和原理可以让我们更好的理解锁和其它同步工具的底层实现。
阻塞和唤醒线程——LockSupport功能简介及原理浅析的更多相关文章
- Java并发包源码学习系列:挂起与唤醒线程LockSupport工具类
目录 LockSupport概述 park与unpark相关方法 中断演示 blocker的作用 测试无blocker 测试带blocker JDK提供的demo 总结 参考阅读 系列传送门: Jav ...
- J.U.C之AQS:阻塞和唤醒线程
此篇博客所有源码均来自JDK 1.8 在线程获取同步状态时如果获取失败,则加入CLH同步队列,通过通过自旋的方式不断获取同步状态,但是在自旋的过程中则需要判断当前线程是否需要阻塞,其主要方法在acqu ...
- 【Java并发编程实战】----- AQS(三):阻塞、唤醒:LockSupport
在上篇博客([Java并发编程实战]----- AQS(二):获取锁.释放锁)中提到,当一个线程加入到CLH队列中时,如果不是头节点是需要判断该节点是否需要挂起:在释放锁后,需要唤醒该线程的继任节点 ...
- AQS阻塞唤醒工具LockSupport
LockSupport在JDK源码中描述为:构建锁和其他同步类的基本线程阻塞原语,构建更高级别的同步工具集.LockSupport提供的park/unpark从线程的粒度上进行阻塞和唤醒,park/u ...
- 【并发】3、LockSupport阻塞与唤醒,相较与wait和notify
我们可以使用wait和notify分别对象线程进行阻塞或者唤醒,但是我们也可以使用LockSupport实现一样的功能,并且在实际使用的时候,个人感觉LockSupport会更加顺手 范例1,wait ...
- 多线程之Java线程阻塞与唤醒
线程的阻塞和唤醒在多线程并发过程中是一个关键点,当线程数量达到很大的数量级时,并发可能带来很多隐蔽的问题.如何正确暂停一个线程,暂停后又如何在一个要求的时间点恢复,这些都需要仔细考虑的细节.在Java ...
- 从JDK源码角度看线程的阻塞和唤醒
目前在Java语言层面能实现阻塞唤醒的方式一共有三种:suspend与resume组合.wait与notify组合.park与unpark组合.其中suspend与resume因为存在无法解决的竟态问 ...
- Java并发框架——AQS之阻塞与唤醒
根据前面的线程阻塞与唤醒小节知道,目前在Java语言层面能实现阻塞唤醒的方式一共有三种:suspend与resume组合.wait与notify组合.park与unpark组合.其中suspend与r ...
- 深入理解Object提供的阻塞和唤醒API
深入理解Object提供的阻塞和唤醒API 前提 前段时间花了大量时间去研读JUC中同步器AbstractQueuedSynchronizer的源码实现,再结合很久之前看过的一篇关于Object提供的 ...
随机推荐
- 项目代码部署百度云(使用git部署,node环境)
学习做了一个小demo,总是在自己的电脑,和局域网上运行很没意思,现在就做点有意思的事情,将代码部署百度云. 1)首先你得进入百度云(直接百度,先要注册一个账号) 2)点击那个“应用引擎”,就会进入 ...
- 关于ng的过滤器的详细解释angular-filter
在使用ng框架做项目的时候,我们可能会使用到ng自带的过滤器,来进行数据的筛选 一:ng自带的过滤器:currency ,date,limitTo,lowercase,uppercase,number ...
- eclipse git 开发操作流程
1.eclipse git 开发操作流程 1.1流程简介 1)master主分支,当开发版本得到了充分的验证之后,才能将分支合入master,master为可产品化发布的状态. 2)develop分支 ...
- new与malloc的区别,以及内存分配浅析
从函数声明上可以看出.malloc 和 new 至少有两个不同: new 返回指定类型的指针,并且可以自动计算所需要大小.比如: 1 2 3 int *p; p = new int; //返回类型 ...
- NumPy-快速处理数据--矩阵运算
本文摘自<用Python做科学计算>,版权归原作者所有. 1. NumPy-快速处理数据--ndarray对象--数组的创建和存取 2. NumPy-快速处理数据--ndarray对象-- ...
- Snippet取表字段说明和详细信息
IF OBJECT_ID (N'dbo.GetDetails', N'IF') IS NOT NULL DROP FUNCTION dbo.GetDetails; GO create function ...
- Linux:WebServer(Nginx 虚拟主机配置与伪静态实现)
ps + 查看方式 | grep + 服务/端口/软件等:查看状态: 一.基本操作 Nginx 多用于商业系统: 一个端口只能被一个服务使用: Nginx 可以同时监听多个端口,也就是配置时, ...
- 关于adb devices连不上手机的几种情况
启动adb 1.打开cmd首次使用adb devices,出现下面这两句,就是正在启动adb服务 C:\Users\admin>adb devices List of devices attac ...
- AngularJS.js: temple
ylbtech-AngularJS.js: temple 1.返回顶部 2.返回顶部 3.返回顶部 4.返回顶部 5.返回顶部 6.返回顶部 7.返回顶部 8.返回顶部 ...
- ftp之心脏病
FTP基础 FTP只通过TCP连接,没有用于FTP的UDP组件.FTP不同于其他服务的是它使用了两个端口, 一个数据端口和一个命令端口(或称为控制端口).通常21端口是命令端口,20端口是数据端口.当 ...