在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功能简介及原理浅析的更多相关文章

  1. Java并发包源码学习系列:挂起与唤醒线程LockSupport工具类

    目录 LockSupport概述 park与unpark相关方法 中断演示 blocker的作用 测试无blocker 测试带blocker JDK提供的demo 总结 参考阅读 系列传送门: Jav ...

  2. J.U.C之AQS:阻塞和唤醒线程

    此篇博客所有源码均来自JDK 1.8 在线程获取同步状态时如果获取失败,则加入CLH同步队列,通过通过自旋的方式不断获取同步状态,但是在自旋的过程中则需要判断当前线程是否需要阻塞,其主要方法在acqu ...

  3. 【Java并发编程实战】----- AQS(三):阻塞、唤醒:LockSupport

    在上篇博客([Java并发编程实战]----- AQS(二):获取锁.释放锁)中提到,当一个线程加入到CLH队列中时,如果不是头节点是需要判断该节点是否需要挂起:在释放锁后,需要唤醒该线程的继任节点 ...

  4. AQS阻塞唤醒工具LockSupport

    LockSupport在JDK源码中描述为:构建锁和其他同步类的基本线程阻塞原语,构建更高级别的同步工具集.LockSupport提供的park/unpark从线程的粒度上进行阻塞和唤醒,park/u ...

  5. 【并发】3、LockSupport阻塞与唤醒,相较与wait和notify

    我们可以使用wait和notify分别对象线程进行阻塞或者唤醒,但是我们也可以使用LockSupport实现一样的功能,并且在实际使用的时候,个人感觉LockSupport会更加顺手 范例1,wait ...

  6. 多线程之Java线程阻塞与唤醒

    线程的阻塞和唤醒在多线程并发过程中是一个关键点,当线程数量达到很大的数量级时,并发可能带来很多隐蔽的问题.如何正确暂停一个线程,暂停后又如何在一个要求的时间点恢复,这些都需要仔细考虑的细节.在Java ...

  7. 从JDK源码角度看线程的阻塞和唤醒

    目前在Java语言层面能实现阻塞唤醒的方式一共有三种:suspend与resume组合.wait与notify组合.park与unpark组合.其中suspend与resume因为存在无法解决的竟态问 ...

  8. Java并发框架——AQS之阻塞与唤醒

    根据前面的线程阻塞与唤醒小节知道,目前在Java语言层面能实现阻塞唤醒的方式一共有三种:suspend与resume组合.wait与notify组合.park与unpark组合.其中suspend与r ...

  9. 深入理解Object提供的阻塞和唤醒API

    深入理解Object提供的阻塞和唤醒API 前提 前段时间花了大量时间去研读JUC中同步器AbstractQueuedSynchronizer的源码实现,再结合很久之前看过的一篇关于Object提供的 ...

随机推荐

  1. 【DUBBO】dubbo架构详解(转载)

    转载地址:http://shiyanjun.cn/archives/325.html Dubbo是Alibaba开源的分布式服务框架,它最大的特点是按照分层的方式来架构,使用这种方式可以使各个层之间解 ...

  2. 学习笔记:AJAX 跨域问题

    学习笔记:AJAX 跨域问题 AJAX 跨域是浏览器的问题. 只要 xhr 请求,不同的域名就会出现 AJAX 跨域问题. JSONP 是一要简单方式,但是有很多弊端,需要修改服务端代码. JSONP ...

  3. 云原生应用基金会CNCF

    2006 年 8 月 9 日,埃里克·施密特(EricSchmidt)在搜索引擎大会上首次提出了“云计算”(Cloud Computing)的概念.一转眼十年过去了,它的发展势如破竹,不断渗透当代的 ...

  4. springboot注册bean失败

    启动的主类应该放在和其他包一样的目录,不能放在一个目录里面

  5. 修改配置文件matplotlibrc,让Matplotlib显示中文

    matplotlib默认不支持中文显示,网上的解决办法有好多种,但是大多数都是通过在代码中指定字体,虽然也能实现,但是多出那么几行代码让人觉得很恶心. 本文介绍一种通过修改配置文件matplotlib ...

  6. 语义分割【semantic-segmentation】资料备忘

    https://github.com/mrgloom/awesome-semantic-segmentation

  7. Unix网络编程 3.9 readline函数

    其实看APUE时就想试着写些简单的stdio函数了,但是一直没实践,看到这里时发现书上写得不完整,便敲代码试了下. 第1个readline速度非常慢原因在于每次读取字符都执行了系统调用read(),而 ...

  8. 使用wifi网卡笔记5---AP模式

    使用WIFI网卡的AP功能 1.下载源码 hostapd: http://w1.fi/hostapd/ 2.编译.安装(hostapd依赖于libnl库,需要编译.安装此库) tar xzf host ...

  9. Oracle block 格式

    Oracle block 格式 信息参考:  http://www.ixora.com.au/ 特别感谢 overtime 大哥对我的无私的帮助和对我一直鼓励支持我的网友这些资料是没得到oracle ...

  10. Word域介绍文章

    https://www.cnblogs.com/ahuo/archive/2007/05/04/735520.html pageref 书签名 :返回书签所在页码 styleref 标题 1 在当前位 ...