设为首页收藏本站
网站公告 | 这是第一条公告
     

 找回密码
 立即注册
缓存时间01 现在时间01 缓存数据 当你走完一段之后回头看,你会发现,那些真正能被记得的事真的是没有多少,真正无法忘记的人屈指可数,真正有趣的日子不过是那么一些,而真正需要害怕的也是寥寥无几。

当你走完一段之后回头看,你会发现,那些真正能被记得的事真的是没有多少,真正无法忘记的人屈指可数,真正有趣的日子不过是那么一些,而真正需要害怕的也是寥寥无几。

查看: 258|回复: 1

我们来说说Java LockSupport 的 park 和 unpark

[复制链接]

  离线 

TA的专栏

  • 打卡等级:无名新人
  • 打卡总天数:1
  • 打卡月天数:0
  • 打卡总奖励:7
  • 最近打卡:2024-09-12 21:09:36
等级头衔

等級:晓枫资讯-列兵

在线时间
0 小时

积分成就
威望
0
贡献
36
主题
30
精华
0
金钱
115
积分
70
注册时间
2023-10-2
最后登录
2025-9-9

发表于 2025-8-28 02:13:56 | 显示全部楼层 |阅读模式

一、LockSupport

LockSupport是JDK中比较底层的类,用来创建锁和其他同步工具类的基本线程阻塞原语。
Java锁和同步器框架的核心AQS:AbstractQueuedSynchronizer,就是通过调用

  1. LockSupport.park()
复制代码
  1. LockSupport.unpark()
复制代码
实现线程的阻塞和唤醒的。LockSupport很类似于二元信号量(只有1个许可证可供使用),如果这个许可还没有被占用,当前线程获取许可并继续执行;如果许可已经被占用,当前线程阻塞,等待获取许可。
LockSupport中的
  1. park()
复制代码
 和 
  1. unpark()
复制代码
 的作用分别是阻塞线程和解除阻塞线程,而且
  1. park()
复制代码
  1. unpark()
复制代码
不会遇到“Thread.suspend 和 Thread.resume所可能引发的死锁”问题。因为
  1. park()
复制代码
 和 
  1. unpark()
复制代码
有许可的存在;调用 
  1. park()
复制代码
 的线程和另一个试图将其 
  1. unpark()
复制代码
 的线程之间的竞争将保持活性。 

1.1、LockSupport函数列表

  1. public class LockSupport {
  2. // 返回提供给最近一次尚未解除阻塞的 park 方法调用的 blocker 对象,如果该调用不受阻塞,则返回 null。
  3. static Object getBlocker(Thread t);
  4. // 为了线程调度,禁用当前线程,除非许可可用。
  5. static void park();
  6. // 为了线程调度,在许可可用之前禁用当前线程。
  7. static void park(Object blocker);
  8. // 为了线程调度禁用当前线程,最多等待指定的等待时间,除非许可可用。
  9. static void parkNanos(long nanos);
  10. // 为了线程调度,在许可可用前禁用当前线程,并最多等待指定的等待时间。
  11. static void parkNanos(Object blocker, long nanos);
  12. // 为了线程调度,在指定的时限前禁用当前线程,除非许可可用。
  13. static void parkUntil(long deadline);
  14. // 为了线程调度,在指定的时限前禁用当前线程,除非许可可用。
  15. static void parkUntil(Object blocker, long deadline);
  16. // 如果给定线程的许可尚不可用,则使其可用。
  17. static void unpark(Thread thread);
  18. }
复制代码

说明:LockSupport是通过调用Unsafe函数中的接口实现阻塞和解除阻塞的。 

1.2、基本使用

  1. // 暂停当前线程
  2. LockSupport.park();
  3. // 恢复某个线程的运行
  4. LockSupport.unpark(暂停线程对象)
复制代码

先 park 再 unpark

  1. Thread t1 = new Thread(() -> {
  2. log.debug("start...");
  3. sleep(1);
  4. log.debug("park...");
  5. LockSupport.park();
  6. log.debug("resume...");
  7. },"t1");
  8. t1.start();
  9. sleep(2);
  10. log.debug("unpark...");
  11. LockSupport.unpark(t1);
复制代码

输出:

  1. 18:42:52.585 c.TestParkUnpark [t1] - start...
  2. 18:42:53.589 c.TestParkUnpark [t1] - park...
  3. 18:42:54.583 c.TestParkUnpark [main] - unpark...
  4. 18:42:54.583 c.TestParkUnpark [t1] - resume...
复制代码

先 unpark 再 park

  1. Thread t1 = new Thread(() -> {
  2. log.debug("start...");
  3. sleep(2);
  4. log.debug("park...");
  5. LockSupport.park();
  6. log.debug("resume...");
  7. }, "t1");
  8. t1.start();
  9. sleep(1);
  10. log.debug("unpark...");
  11. LockSupport.unpark(t1);
复制代码

输出:

  1. 18:43:50.765 c.TestParkUnpark [t1] - start...
  2. 18:43:51.764 c.TestParkUnpark [main] - unpark...
  3. 18:43:52.769 c.TestParkUnpark [t1] - park...
  4. 18:43:52.769 c.TestParkUnpark [t1] - resume...
复制代码

1.3、特点

在调用对象的Wait之前当前线程必须先获得该对象的监视器(Synchronized),被唤醒之后需要重新获取到监视器才能继续执行。而LockSupport并不需要获取对象的监视器。 

与 Object 的 wait & notify 相比

  • 1、wait,notify 和 notifyAll 必须配合 Object Monitor 一起使用,而 park,unpark 不必。
  • 2、park & unpark 是以线程为单位来【阻塞】和【唤醒】线程,而 notify 只能随机唤醒一个等待线程,notifyAll是唤醒所有等待线程,但不那么【精确】。
  • 3、park & unpark 可以先 unpark,而 wait & notify 不能先 notify。

因为它们本身的实现机制不一样,所以它们之间没有交集,也就是说LockSupport阻塞的线程,notify/notifyAll没法唤醒。
虽然两者用法不同,但是有一点, LockSupport 的park和Object的wait一样也能响应中断。

  1. public class LockSupportTest {
  2. public static void main(String[] args) throws InterruptedException {
  3. Thread t = new Thread(() -> {
  4. LockSupport.park();
  5. System.out.println("thread:"+Thread.currentThread().getName()+"awake");
  6. },"t1");
  7. t.start();
  8. Thread.sleep(2000);
  9. //中断
  10. t.interrupt();
  11. }
  12. }
复制代码

二、LockSupport park & unpark原理

每个线程都会关联一个 Parker 对象,每个 Parker 对象都各自维护了三个角色:

  1. _counter
复制代码
(计数器)、 
  1. _mutex
复制代码
(互斥量)、
  1. _cond
复制代码
(条件变量)。 

2.1、情况一,先调用park,再调用unpark

park 操作

1.jpeg

  1. 当前线程调用 
    1. Unsafe.park()
    复制代码
     方法
  2. 检查 
    1. _counter
    复制代码
     ,本情况为 0,这时,获得 
    1. _mutex
    复制代码
     互斥锁
  3. 线程进入 
    1. _cond
    复制代码
     条件变量阻塞
  4. 设置 

    1. _counter = 0
    复制代码
     

    unpark 操作

    2.jpeg

  5. 调用 

    1. Unsafe.unpark(Thread_0)
    复制代码
     方法,设置 
    1. _counter
    复制代码
     为 1

  6. 唤醒 
    1. _cond
    复制代码
     条件变量中的 
    1. Thread_0
    复制代码
    1. Thread_0
    复制代码
     恢复运行
  7. 设置 

    1. _counter
    复制代码
     为 0 

    2.2、情况二,先调用unpark,再调用park

    3.jpeg

  8. 调用 

    1. Unsafe.unpark(Thread_0)
    复制代码
     方法,设置 
    1. _counter
    复制代码
     为 1

  9. 当前线程调用 
    1. Unsafe.park()
    复制代码
     方法
  10. 检查 
    1. _counter
    复制代码
     ,本情况为 1,这时线程无需阻塞,继续运行
  11. 设置 

    1. _counter
    复制代码
     为 0 

    三、LockSupport Java源码解析

    3.1 变量说明

    1. public class LockSupport {
    2. // Hotspot implementation via intrinsics API
    3. //unsafe常量,设置为使用Unsafe.compareAndSwapInt进行更新
    4. //UNSAFE字段表示sun.misc.Unsafe类,一般程序中不允许直接调用
    5. private static final sun.misc.Unsafe UNSAFE;
    6. //表示parkBlocker在内存地址的偏移量
    7. private static final long parkBlockerOffset;
    8. //表示threadLocalRandomSeed在内存地址的偏移量,此变量的作用暂时还不了解
    9. private static final long SEED;
    10. //表示threadLocalRandomProbe在内存地址的偏移量,此变量的作用暂时还不了解
    11. private static final long PROBE;
    12. //表示threadLocalRandomSecondarySeed在内存地址的偏移量
    13. // 作用是 可以通过nextSecondarySeed()方法来获取随机数
    14. private static final long SECONDARY;
    15. }
    复制代码

    变量是如何获取其实例对象的?

    1. public class LockSupport {
    2. static {
    3. try {
    4. //实例化unsafe对象
    5. UNSAFE = sun.misc.Unsafe.getUnsafe();
    6. Class<?> tk = Thread.class;
    7. //利用unsafe对象来获取parkBlocker在内存地址的偏移量
    8. parkBlockerOffset = UNSAFE.objectFieldOffset(tk.getDeclaredField("parkBlocker"));
    9. //利用unsafe对象来获取threadLocalRandomSeed在内存地址的偏移量
    10. SEED = UNSAFE.objectFieldOffset(tk.getDeclaredField("threadLocalRandomSeed"));
    11. //利用unsafe对象来获取threadLocalRandomProbe在内存地址的偏移量
    12. PROBE = UNSAFE.objectFieldOffset(tk.getDeclaredField("threadLocalRandomProbe"));
    13. //利用unsafe对象来获取threadLocalRandomSecondarySeed在内存地址的偏移量
    14. SECONDARY = UNSAFE.objectFieldOffset(tk.getDeclaredField("threadLocalRandomSecondarySeed"));
    15. } catch (Exception ex) { throw new Error(ex); }
    16. }
    17. }
    复制代码

    由上面代码可知这些变量是通过

    1. static
    复制代码
    代码块在类加载的时候就通过
    1. unsafe
    复制代码
    对象获取其在内存地址的偏移量了。 

    3.2 构造方法

    1. public class LockSupport {
    2. //LockSupport只有一个私有构造函数,无法被实例化。
    3. private LockSupport() {} // Cannot be instantiated.
    4. }
    复制代码

    3.3 两个特殊的方法

    1. public class LockSupport {
    2. //设置线程t的parkBlocker字段的值为arg
    3. private static void setBlocker(Thread t, Object arg) {
    4. // Even though volatile, hotspot doesn't need a write barrier here.
    5. //尽管hotspot易变,但在这里并不需要写屏障。
    6. UNSAFE.putObject(t, parkBlockerOffset, arg);
    7. }
    8. //获取当前线程的Blocker值
    9. public static Object getBlocker(Thread t) {
    10. //若当前线程为空就抛出异常
    11. if (t == null)
    12. throw new NullPointerException();
    13. //利用unsafe对象获取当前线程的Blocker值
    14. return UNSAFE.getObjectVolatile(t, parkBlockerOffset);
    15. }
    16. }
    复制代码

    1、unpark(Thread thread)方法

    1. public class LockSupport {
    2. //释放该线程的阻塞状态,即类似释放锁,只不过这里是将许可设置为1
    3. public static void unpark(Thread thread) {
    4. //判断线程是否为空
    5. if (thread != null)
    6. //释放该线程许可
    7. UNSAFE.unpark(thread);
    8. }
    9. }
    复制代码

    2、park(Object blocker)方法 和park()方法

    1. public class LockSupport {
    2. //阻塞当前线程,并且将当前线程的parkBlocker字段设置为blocker
    3. public static void park(Object blocker) {
    4. //获取当前线程
    5. Thread t = Thread.currentThread();
    6. //将当前线程的parkBlocker字段设置为blocker
    7. setBlocker(t, blocker);
    8. //阻塞当前线程,第一个参数表示isAbsolute,是否为绝对时间,第二个参数就是代表时间
    9. UNSAFE.park(false, 0L);
    10. //重新可运行后再此设置Blocker
    11. setBlocker(t, null);
    12. }
    13. //无限阻塞线程,直到有其他线程调用unpark方法
    14. public static void park() {
    15. UNSAFE.park(false, 0L);
    16. }
    17. }
    复制代码

    说明:

  • 调用

    1. park
    复制代码
    函数时,首先获取当前线程,然后设置当前线程的
    1. parkBlocker
    复制代码
    字段,即调用
    1. setBlocker
    复制代码
    函数, 之后调用
    1. Unsafe
    复制代码
    类的
    1. park
    复制代码
    函数,之后再调用
    1. setBlocker
    复制代码
    函数。 

    1. park(Object blocker)
    复制代码
    函数中要调用两次
    1. setBlocker
    复制代码
    函数

  • 1、调用

    1. park
    复制代码
    函数时,当前线程首先设置好
    1. parkBlocker
    复制代码
    字段,然后再调用 
    1. Unsafe
    复制代码
    的park函数,此时,当前线程就已经阻塞了,等待该线程的
    1. unpark
    复制代码
    函数被调用,所以后面的一个 
    1. setBlocker
    复制代码
    函数无法运行,
    1. unpark
    复制代码
    函数被调用,该线程获得许可后,就可以继续运行了,也就运行第二个 
    1. setBlocker
    复制代码
    ,把该线程的
    1. parkBlocker
    复制代码
    字段设置为null,这样就完成了整个
    1. park
    复制代码
    函数的逻辑。

  • 2、如果没有第二个 
    1. setBlocker
    复制代码
    ,那么之后没有调用
    1. park(Object blocker)
    复制代码
    ,而直接调用
    1. getBlocker
    复制代码
    函数,得到的还是前一个 
    1. park(Object blocker)
    复制代码
    设置的
    1. blocker
    复制代码
    ,显然是不符合逻辑的。总之,必须要保证在
    1. park(Object blocker)
    复制代码
    整个函数 执行完后,该线程的
    1. parkBlocker
    复制代码
    字段又恢复为null。

所以,

  1. park(Object)
复制代码
型函数里必须要调用
  1. setBlocker
复制代码
函数两次。 

3、parkNanos(Object blocker, long nanos)方法 和parkNanos(long nanos)方法

  1. public class LockSupport {
  2. //阻塞当前线程nanos秒
  3. public static void parkNanos(Object blocker, long nanos) {
  4. //先判断nanos是否大于0,小于等于0都代表无限等待
  5. if (nanos > 0) {
  6. //获取当前线程
  7. Thread t = Thread.currentThread();
  8. //将当前线程的parkBlocker字段设置为blocker
  9. setBlocker(t, blocker);
  10. //阻塞当前线程现对时间的nanos秒
  11. UNSAFE.park(false, nanos);
  12. //将当前线程的parkBlocker字段设置为null
  13. setBlocker(t, null);
  14. }
  15. }
  16. //阻塞当前线程nanos秒,现对时间
  17. public static void parkNanos(long nanos) {
  18. if (nanos > 0)
  19. UNSAFE.park(false, nanos);
  20. }
  21. }
复制代码

4、parkUntil(Object blocker, long deadline)方法 和parkUntil(long deadline)方法

  1. public class LockSupport {
  2. //将当前线程阻塞绝对时间的deadline秒,并且将当前线程的parkBlockerOffset设置为blocker
  3. public static void parkUntil(Object blocker, long deadline) {
  4. //获取当前线程
  5. Thread t = Thread.currentThread();
  6. //设置当前线程parkBlocker字段设置为blocker
  7. setBlocker(t, blocker);
  8. //阻塞当前线程绝对时间的deadline秒
  9. UNSAFE.park(true, deadline);
  10. //当前线程parkBlocker字段设置为null
  11. setBlocker(t, null);
  12. }
  13. //将当前线程阻塞绝对时间的deadline秒
  14. public static void parkUntil(long deadline) {
  15. UNSAFE.park(true, deadline);
  16. }
  17. }
复制代码

总结:

LockSupport 和 CAS 是Java并发包中很多并发工具控制机制的基础,它们底层其实都是依赖Unsafe实现。很多锁的类都是基于LockSupport的park和unpark来实现的,所以了解LockSupport类是非常重要的。

到此这篇关于我们来说说Java LockSupport 的 park 和 unpark的文章就介绍到这了,更多相关java LockSupport 的 park 和 unpark内容请搜索晓枫资讯以前的文章或继续浏览下面的相关文章希望大家以后多多支持晓枫资讯!


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
晓枫资讯-科技资讯社区-免责声明
免责声明:以上内容为本网站转自其它媒体,相关信息仅为传递更多信息之目的,不代表本网观点,亦不代表本网站赞同其观点或证实其内容的真实性。
      1、注册用户在本社区发表、转载的任何作品仅代表其个人观点,不代表本社区认同其观点。
      2、管理员及版主有权在不事先通知或不经作者准许的情况下删除其在本社区所发表的文章。
      3、本社区的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,举报反馈:点击这里给我发消息进行删除处理。
      4、本社区一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
      5、以上声明内容的最终解释权归《晓枫资讯-科技资讯社区》所有。
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~

  离线 

TA的专栏

等级头衔

等級:晓枫资讯-列兵

在线时间
0 小时

积分成就
威望
0
贡献
0
主题
0
精华
0
金钱
21
积分
22
注册时间
2022-12-27
最后登录
2022-12-27

发表于 2025-9-2 09:09:47 | 显示全部楼层
顶顶更健康!!!
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~
严禁发布广告,淫秽、色情、赌博、暴力、凶杀、恐怖、间谍及其他违反国家法律法规的内容。!晓枫资讯-社区
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

1楼
2楼

手机版|晓枫资讯--科技资讯社区 本站已运行

CopyRight © 2022-2025 晓枫资讯--科技资讯社区 ( BBS.yzwlo.com ) . All Rights Reserved .

晓枫资讯--科技资讯社区

本站内容由用户自主分享和转载自互联网,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。

如有侵权、违反国家法律政策行为,请联系我们,我们会第一时间及时清除和处理! 举报反馈邮箱:点击这里给我发消息

Powered by Discuz! X3.5

快速回复 返回顶部 返回列表