JUC 包下工具类,它的名字叫 LockSupport !你造么?
前言
LockSupport 是 JUC 中常用的一个工具类,主要作用是挂起和唤醒线程。在阅读 JUC 源码中经常看到,所以很有必要了解一下。
公众号:liuzhihangs ,记录工作学习中的技术、开发及源码笔记;时不时分享一些生活中的见闻感悟。欢迎大佬来指导!
介绍
基本线程阻塞原语创建锁和其他同步类。Basic thread blocking primitives for creating locks and other synchronization classes.
LockSupport 类每个使用它的线程关联一个许可(在意义上的Semaphore类)。 如果许可可用,调用 park 将立即返回,并在此过程中消费它; 否则可能阻塞。如果许可不是可用,可以调用 unpark 使得许可可用。(但与Semaphore不同,许可不能累积。最多有一个。)
方法 park 和 unpark 提供了阻塞的有效手段和解锁线程不会遇到死锁问题,而 Thread.suspend 和 Thread.resume 是不能用于这种目的:因为许可的存在,一个线程调用 park 和另一个线程试图 unpark 它之间的竞争将保持活性。 此外,如果调用者线程被中断,park 将返回,并且支持设置超时。 该 park 方法也可能返回在其他任何时间,“毫无理由”,因此通常必须在一个循环中调用的返回后重新检查条件。 在这个意义上park作为“忙碌等待”不会浪费太多的时间自旋的优化,但必须以配对 unpark 使用。
这三种形式的 park 还支持 blocker 对象参数。而线程被阻塞时是允许使用监测和诊断工具,以确定线程被阻塞的原因。(诊断工具可以使用getBlocker(Thread) 方法 。)同时推荐使用带有 blocker 参数的 park方法,通常做法是 blocker 被设置为 this 。
上面的意思总结下来个人理解是:
- 许可(permit)的上限是1,也就是说只有 0 或 1 。
- park: 没有许可的时候,permit 为 0 ,调用 park 会阻塞;有许可的时候,permit 为 1 , 调用 park 会扣除一个许可,然后返回。
- unpark:没有许可的时候,permit 为 0 ,调用 unpark 会增加一个许可,因为许可上限是 1 , 所以调用多次也只会为 1 个。
- 线程初始的时候是没有许可的。
- park 的当前线程如果被中断,会立即返回,并不会抛出中断异常。
- park 方法的调用一般要放在一个循环判断体里面。
大概如图所示:

下面是源码注释中的案例:
/**
* FIFO 独占锁
*/
class FIFOMutex {
private final AtomicBoolean locked = new AtomicBoolean(false);
private final Queue<Thread> waiters = new ConcurrentLinkedQueue<Thread>();
public void lock() {
boolean wasInterrupted = false;
Thread current = Thread.currentThread();
waiters.add(current);
// Block while not first in queue or cannot acquire lock
// 不在队列头,或者锁被占用,则阻塞, 就是只有队列头的可以获得锁
while (waiters.peek() != current || !locked.compareAndSet(false, true)) {
LockSupport.park(this);
if (Thread.interrupted()) // ignore interrupts while waiting
wasInterrupted = true;
}
waiters.remove();
if (wasInterrupted) // reassert interrupt status on exit
current.interrupt();
}
public void unlock() {
locked.set(false);
LockSupport.unpark(waiters.peek());
}
}
验证
线程初始有没有许可?
public class LockSupportTest {
public static void main(String[] args) {
System.out.println("开始执行……");
LockSupport.park();
System.out.println("LockSupport park 之后……");
}
}
- 执行后会发现,代码在 park 处阻塞。说明,线程初始是没有许可的。
添加许可并消耗许可
public class LockSupportTest {
public static void main(String[] args) {
System.out.println("开始执行……");
LockSupport.unpark(Thread.currentThread());
System.out.println("执行 - park");
LockSupport.park();
System.out.println("LockSupport park 之后……");
}
}
public class LockSupportTest {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程 " + Thread.currentThread().getName() + "开始执行 park");
LockSupport.park(this);
System.out.println("线程 " + Thread.currentThread().getName() + "执行 park 结束");
}
});
thread.start();
// 保证 上面线程先执行,然后再主线程
Thread.sleep(5000);
System.out.println("开始执行 unpark(thread)");
LockSupport.unpark(thread);
Thread.sleep(5000);
System.out.println("执行 unpark(thread) 结束");
}
}
通过上面示例可以看出:
- 执行 unpark 可以进行给予指定线程一个证书。
- 线程当前被 park 阻塞,此时给予证书之后, park 会消耗证书,然后继续执行。
许可上限为 1
public class LockSupportTest {
public static void main(String[] args) {
System.out.println("unpark 1次");
LockSupport.unpark(Thread.currentThread());
System.out.println("unpark 2次");
LockSupport.unpark(Thread.currentThread());
System.out.println("执行 - park 1 次");
LockSupport.park();
System.out.println("执行 - park 2 次");
LockSupport.park();
System.out.println("LockSupport park 之后……");
}
}
- 线程阻塞,可以看出 permit 只能有一个
中断可以使 park 继续执行并不会抛出异常
public class LockSupportTest {
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程 " + Thread.currentThread().getName() + "开始执行 park");
LockSupport.park(this);
System.out.println("线程 " + Thread.currentThread().getName() + "执行 park 结束");
System.out.println("线程 " + Thread.currentThread().getName() + "开始执行 park 第二次");
LockSupport.park(this);
System.out.println("线程 " + Thread.currentThread().getName() + "执行 park 第二次 结束");
}
});
try {
thread.start();
// 保证 上面线程先执行,然后再主线程
Thread.sleep(5000);
System.out.println("开始执行 unpark(thread)");
// LockSupport.unpark(thread);
thread.interrupt();
Thread.sleep(5000);
System.out.println("执行 unpark(thread) 结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
输出结果:
/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/bin/java ...
线程 Thread-0开始执行 park
开始执行 unpark(thread)
线程 Thread-0执行 park 结束
线程 Thread-0开始执行 park 第二次
线程 Thread-0执行 park 第二次 结束
执行 unpark(thread) 结束
- 可以看出线程中断,park 会继续执行,并且没有抛出异常。
- thread.interrupt(); 调用之后, 设置线程中断标示,unpark 没有清除中断标示,第二个 park 也会继续执行。
使用诊断工具
liuzhihang % > jps
76690 LockSupportTest
77130 Jps
liuzhihang % > jstack 77265
...
"main" #1 prio=5 os_prio=31 tid=0x00007f7f3e80a000 nid=0xe03 waiting on condition [0x000070000dfcd000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:304)
at com.liuzhihang.source.LockSupportTest.main(LockSupportTest.java:14)
- 中间省略部分,但是可以看出线程进入
WAITING状态
源码分析
public class LockSupport {
private static final sun.misc.Unsafe UNSAFE;
/**
* 为线程 thread 设置一个许可
* 无许可,则添加一个许可,有许可,则不添加
* 如果线程因为 park 被阻塞, 添加许可之后,会解除阻塞状态
*/
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
/**
* 有许可,则使用该许可
* 没有许可,阻塞线程,直到获得许可
* 传递 blocker 是为了方便使用诊断工具
*/
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, 0L);
setBlocker(t, null);
}
/**
* 设置线程的 blocker 属性
*/
private static void setBlocker(Thread t, Object arg) {
// Even though volatile, hotspot doesn't need a write barrier here.
UNSAFE.putObject(t, parkBlockerOffset, arg);
}
}
LockSupport 的 park unpark 方法,实际调用的是底层 Unsafe 类的 native 方法。
public final class Unsafe {
public native void unpark(Object var1);
public native void park(boolean var1, long var2);
}
既然调用了 Unsafe 到此处肯定不能善罢甘休。

hotspot 源码
这块是下载的官方包中的源码,阅读并查阅资料了解的大概逻辑,不清楚之处,希望指导出来。
也可以直接跳过直接看结论。
查看jdk源码
http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/5a83b7215107/src/share/vm/runtime/park.hpp
这块在以 os_linux 代码为例
http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/5a83b7215107/src/os/linux/vm/os_linux.cpp



- 在底层维护了一个
_counter通过更新_counter的值来标示是否有证明。 - 在 park 时,判断
_counter为 0,则阻塞等待,为 1 则获得更新为 0 并返回。 - 在 unpark 时,判断
_counter为 0,则给予凭证,并唤醒线程,为 1 则直接返回。
总结
总结也是和预想的是相同的。
- 许可(permit)的上限是1,也就是说只有 0 或 1 。
- park: 没有许可的时候,permit 为 0 ,调用 park 会阻塞;有许可的时候,permit 为 1 , 调用 park 会扣除一个许可,然后返回。
- unpark:没有许可的时候,permit 为 0 ,调用 unpark 会增加一个许可,因为许可上限是 1 , 所以调用多次也只会为 1 个。
- 线程初始的时候是没有许可的。
- park 的当前线程如果被中断,会立即返回,并不会抛出中断异常。
扩展
- park/unpark 和 wait/notify 区别
- park 阻塞当前线程,unpark 唤醒指定线程。
- wait() 需要结合锁使用,并释放锁资源,如果没有设置超时时间,则需要 notify() 唤醒。而 notify() 是随机唤醒线程。
JUC 包下工具类,它的名字叫 LockSupport !你造么?的更多相关文章
- JUC学习笔记--JUC中并发工具类
JUC中并发工具类 CountDownLatch CountDownLatch是我目前使用比较多的类,CountDownLatch初始化时会给定一个计数,然后每次调用countDown() 计数减1, ...
- Android 包信息工具类
/** AndroidInfoUtils:安卓游戏包信息工具类**/ 1 public class AndroidInfoUtils { @SuppressWarnings("uncheck ...
- java 查找指定包下的类
package com.jason.test; import java.io.File; import java.io.IOException; import java.io.UnsupportedE ...
- java.io 包下的类有哪些 + 面试题
java.io 包下的类有哪些 + 面试题 IO 介绍 IO 是 Input/Output 的缩写,它是基于流模型实现的,比如操作文件时使用输入流和输出流来写入和读取文件等. IO 分类 传统的 IO ...
- 【Java多线程】JUC包下的工具类CountDownLatch、CyclicBarrier和Semaphore
前言 JUC中为了满足在并发编程中不同的需求,提供了几个工具类供我们使用,分别是CountDownLatch.CyclicBarrier和Semaphore,其原理都是使用了AQS来实现,下面分别进行 ...
- java工具类 获取包下所有类
extends:http://blog.csdn.net/jdzms23/article/details/17550119 package com.threeti.util; import java. ...
- juc包:使用 juc 包下的显式 Lock 实现线程间通信
一.前置知识 线程间通信三要素: 多线程+判断+操作+通知+资源类. 上面的五个要素,其他三个要素就是普通的多线程程序问题,那么通信就需要线程间的互相通知,往往伴随着何时通信的判断逻辑. 在 java ...
- JUC常用同步工具类——CountDownLatch,CyclicBarrier,Semaphore
在 JUC 下包含了一些常用的同步工具类,今天就来详细介绍一下,CountDownLatch,CyclicBarrier,Semaphore 的使用方法以及它们之间的区别. 一.CountDownLa ...
- JUC : 并发编程工具类的使用
个人博客网:https://wushaopei.github.io/ (你想要这里多有) 一.JUC是什么 1.JUC定义 JUC,即java.util.concurrent 在并发编程中使用的 ...
随机推荐
- spring-boot-route(八)整合mybatis操作数据库
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL.存储过程以及高级映射.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以使用简单的 XML ...
- tu
1 第五章 图 2 //结构定义 3 #define MaxVertexNum 100 //图中顶点数目的最大值 4 typedef struct ArcNode{ //边表节点 5 int adjv ...
- MLHPC 2018 | Aluminum: An Asynchronous, GPU-Aware Communication Library Optimized for Large-Scale Training of Deep Neural Networks on HPC Systems
这篇文章主要介绍了一个名为Aluminum通信库,在这个库中主要针对Allreduce做了一些关于计算通信重叠以及针对延迟的优化,以加速分布式深度学习训练过程. 分布式训练的通信需求 通信何时发生 一 ...
- Linux系统编程—管道
▋****1. 管道的概念 管道,又名「无名管理」,或「匿名管道」,管道是一种非常基本,也是使用非常频繁的IPC方式. 1.1 管道本质 管道的本质也是一种文件,不过是伪文件,实际上是一块内核缓冲区, ...
- Visual C# 制作DLL文件
一.制作.dll1.首先创建一个新类库工程文件 文件->新建->项目->Visual C#->类库.填入工程文件名称,并且选择文件要存放的目录. 2.工程文件 将Class1 ...
- 西安交通大学c++[mooc]课后题12章(只有后两题)
不是从第一题开始的,因为我刚准备把代码粘到CSDN上面,可以给自己看,也有可能启发后来者. 机会是留给有准备的人的 --路易斯·巴斯德 先写下第12周慕课学习总结吧! 多态就是将运算符重载, ...
- Linux nginx 安装 启动
nginx下载地址:https://nginx.org/download/ ## 解压 tar -zxvf nginx-1.9.9.tar.gz ##进入nginx目录 cd nginx-1.9.9 ...
- day18 Pyhton学习 内置函数最后七个
1. enumerate 枚举函数 for i in enumerate(['a','b','c'],1): print(i)#(1, 'a')(2, 'b')(3, 'c') goods_lst= ...
- bash 括号使用
Bash 括号多种使用方式 ${} 变量初始化 ${param:-string} 若变量param为空或者未定义,则用在命令行中用string来替换${param:-string} 否则变量param ...
- 会用Docker的人都别装了,这多简单呐
学术又官方的说法 Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器或Windows 机器上,也可以实现虚拟化,容器是 ...