线程阻塞唤醒工具 LockSupport使用详解
作者:暮色妖娆丶 发布时间:2023-11-29 17:16:10
LockSupport 简介
LockSupport
是 Java 并发编程中一个非常重要的组件,我们熟知的并发组件 Lock、线程池、CountDownLatch
等都是基于 AQS
实现的,而 AQS
内部控制线程阻塞和唤醒又是通过 LockSupport
来实现的。
从该类的注释上也可以发现,它是一个控制线程阻塞和唤醒的工具,与以往的不同是它解决了曾经 wait()、notify()、await()、signal()
的局限。
回顾 synchronized 和 Lock
我们知道 Java 中实现并发安全通常会通过这两种加锁的方式,对于 synchronized
加锁的方式,如果我们想要控制线程的阻塞和唤醒是通过锁对象的 wait()
和 notify()
方法,以下面循环交替打印 AB 为例
int status = 2;
public static void main(String[] args) throws InterruptedException {
TestSync obj = new TestSync();
new Thread(() -> {
synchronized (obj){
while (true){
if(obj.status == 1){
obj.wait();
}
System.out.println("A");
obj.status = 1;
TimeUnit.SECONDS.sleep(1);
obj.notify();
}
}
}).start();
new Thread(() -> {
synchronized (obj){
while (true){
if(obj.status == 2){
obj.wait();
}
System.out.println("B");
obj.status = 2;
TimeUnit.SECONDS.sleep(1);
obj.notify();
}
}
}).start();
}
如果我们使用 Lock
实现类,上述代码几乎是一样的,只是先获取 Condition
对象
Condition condition = lock.newCondition();
把 obj.wait()
换成 condition.await()
, obj.notify()
换成 condition.signal()
即可。
LockSupport 和 synchronized 和 Lock 的阻塞方式对比
技术 | 阻塞唤醒方式 | 局限 |
---|---|---|
synchronized | 使用锁对象的 wait()、notify() | 1. 只能用在 synchronized 包裹的同步代码块中 2. 必须先 wait() 才能 notify() |
Lock | 使用 condition 的 await()、signal() | 1. 只能用在 lock 锁住的代码块中 2. 必须先 await() 才能 signal() |
LockSupport | park()、unpark(Thread t) | 没有限制 |
LockSupport 的使用
下面代码中,我们使用 LockSupport
去阻塞和唤醒线程,我们可以多次尝试,LockSupport
的 park()
和 unpark()
方法没有先后顺序的限制,也不需要捕获异常,也没有限制要在什么代码块中才能使用。
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
System.out.println("A");
LockSupport.park();
System.out.println("被唤醒");
});
t1.start();
TimeUnit.SECONDS.sleep(2);
new Thread(() -> {
System.out.println("B");
LockSupport.unpark(t1);
}).start();
}
LockSupport 注意事项
许可证提前发放
从该类的注释中我们可以看到这个类存储了使用它的线程的一个许可证,当调用 park()
方法的时候会判断当前线程的许可证是否存在,如果存在将直接放行,否则就阻塞。
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("A");
LockSupport.park();//不会阻塞
System.out.println("被唤醒");
});
t1.start();
TimeUnit.SECONDS.sleep(2);
new Thread(() -> {
System.out.println("B");
System.out.println("先调用 unpark()");
LockSupport.unpark(t1);
},"t2").start();
}
看这个代码示例,这里我们在 t2
中先让线程 t1
unpark()
, 然后在 t1
中调用 park()
, 结果并不会阻塞 t1
线程。因为在 t2
中调用 LockSupport.unpark(t1);
的时候相当于给 t1
提前准备好了许可证。
许可证不会累计
LockSupport.unpark(t1);
无论调用多少次,t1
的通行证只有一个,当在 t1
中调用两次 park()
方法时线程依然会被阻塞。
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("A");
LockSupport.park();
LockSupport.park();
System.out.println("被唤醒");
});
t1.start();
TimeUnit.SECONDS.sleep(2);
new Thread(() -> {
System.out.println("B");
System.out.println("先调用 unpark()");
LockSupport.unpark(t1);
LockSupport.unpark(t1);
LockSupport.unpark(t1);
LockSupport.unpark(t1);
LockSupport.unpark(t1);
},"t2").start();
}
以上述代码为例,t1
将被阻塞。
LockSupport 底层实现
观察源码发现 park() 和 unpark() 最底下调用的是 native() 方法,源码在 C++ 中实现
@IntrinsicCandidate
public native void park(boolean isAbsolute, long time);
@IntrinsicCandidate
public native void unpark(Object thread);
对,这只是个标题,卷不动了,不去看 C/C++
了。。。。
来源:https://juejin.cn/post/7192019058399641658


猜你喜欢
- ArrayList底层维护的是一个动态数组,每个ArrayList实例都有一个容量。该容量是指用来存储列表元素的数组的大小。它总是至少等于列
- 如何快速构建一个Spring Boot的项目工具 ideaJDK版本 1.8Spring Boot 版本 1.5.9环境搭建实现:最基础前端
- 1.常用属性Name:名称;BackColor:设置控件背景颜色;Enabled:是否可用;FlayStyle:控件样式;Image:设置控
- 有参数传递的地方都少不了参数校验。在web开发中,前端
- Java NIO读取大文件已经不是什么新鲜事了,但根据网上示例写出的代码来处理具体的业务总会出现一些奇怪的Bug。针对这种情况,我总结了一些
- 1.冒泡排序简介冒泡排序(Bubble Sorting)即:通过对待排序的序列从前往后,依次比较相邻元素的值,若发现逆序则交换位置,使较大的
- 引言一个Java Gradle项目会涉及到资源的访问. 一般情况下会将当前项目所需的资源文件全部放置于resources文件夹下, 无论是m
- 前言倒计时功能在游戏中一直很重要, 不管是活动开放时间,还是技能冷却。 本文实现了一个通用倒计时组件,实现了倒计时的基本功能,支持倒计时结束
- 本文实例为大家分享了C++实现哈夫曼树的编码解码,供大家参考,具体内容如下代码:#pragma once#include<iostre
- 开发环境 android studio 3.0.1 已支持 kotlin1、定义接口interface CallBack{ fun call
- 1 关于自动内存管理Java是由jvm来管理内存,包括自动分配以及自动回收,因此它不容易出现内存泄漏和内存溢出问题。C/C++,由程序员手动
- 本文实例讲述了Android编程自定义对话框(Dialog)位置及大小的方法。分享给大家供大家参考,具体如下:代码:package ange
- 本文实例为大家分享了unity实现鼠标跟随的具体代码,供大家参考,具体内容如下需求:当鼠标放到cube上,然后移开鼠标cube会跟随鼠标移动
- 百度百科说法:Servlet(Server Applet)是Java Servlet的简称,称为小服务程序或服务连接器,用Java编写的服务
- 前言注解是Java很强大的部分,但大多数时候我们倾向于使用而不是去创建注解。例如,在Java源代码里不难找到Java编译器处理的@Overr
- 前言最近在网上看到一篇文章,里面说到:List<T>.FindAll的效率竟然比for循环还差,下面是文章的截图:我在上文代码基
- 本文实例讲述了Java Swing实现简单的体重指数(BMI)计算器功能。分享给大家供大家参考,具体如下:BMI,Body Mass Ind
- 现在版本更新有两种处理方式:跳转到App应用市场,通过应用市场下载更新安装。在App内进行Apk下载,下载完成后更新安装。实现思路:请求后台
- 协议做如下规定:规定数据协议:序列号 长度 状态字 数据长度 数据1 &n
- 1. Stack1.1 介绍Stack 栈是 Vector 的一个子类,它实现了一个标准的后进先出的栈。它的底层是一个数组。堆栈只定义了默认