专栏首页开发技术Java 并发编程(一) → LockSupport 详解

Java 并发编程(一) → LockSupport 详解

开心一刻

  今天突然收到花呗推送的消息,说下个月 9 号需要还款多少钱

  我就纳了闷了,我很长时间没用花呗了,怎么会欠花呗钱?

  后面我一想,儿子这几天玩了我手机,是不是他偷摸用了我的花呗

  于是我找到儿子问了起来

  我:儿子,你是不是用了我的花呗

  儿子:是的呀,爸,我就用了一点

  我:额度就剩两块了,你用了我用什么?

  儿子:你用你爸的呗!

  我:...

  不对呀,我女朋友都没有,哪里的儿子?猛的被惊醒,大白天的,我特么竟然还做上了白日梦!

前言

  本文是基于 JDK1.8

  那么此时 Java 线程与操作系统线程的对应关系是 1:1 的,有兴趣的可以读一读:深入聊聊java线程模型实现?

  至于 Java 是否在未来引入类似 Go 中的协程,从而实现 Java 线程与操作系统线程的关系是 m:n,那是未来的事,那就未来再说

  我们能确定的是:Java8 中,Java 线程与操作系统线程是 1:1 的

LockSupport 简介

  关于 LockSupport,我们对它感到很陌生,因为我们在工作中很少直接接触到它,但多多少少,我们都间接用到过它

  LockSupport 是 JUC 包下很重要的一个工具类,我们来看看它的源码概述:

    Basic thread blocking primitives for creating locks and other synchronization classes

    用于创建锁和其他同步类的基本线程阻塞原语

  JUC 包下的锁、同步类基本都依赖 LockSupport 实现线程的阻塞与唤醒

  我们可以简单的认为 LockSupport 对 Java 线程(操作系统线程)的阻塞与唤醒进行了封装,简化了开发人员的任务

  permit(许可证)

  LockSupport 的设计思路就是为每一个线程设置一个 permit,其实就是一个值,类似于 AQS 中的 state

  但 permit 没有显示的存在于 LockSupport 的源码中,而 state 却显示的存在于 AQS 的源码中( private volatile int state; )

    permit 默认值(初始值)是 0,permit 最小值是 0,最大值是 1;0 表示许可证不可用,1 表示许可证可用

    若 permit 值为 0,则 park 方法会阻塞当前线程,直至超时或有可用的 permit;若 permit 为 1 ,则 park 方法会将 permit 值设置成 0,不会阻塞当前线程

    不管 permit 的值是 0 还是 1,unpark 方法会将 permit 设置成 1,也就说多次 unpark (中间没有 park)后,permit 的值仍是 1

  那么问题来了,permit 不在 LockSupport 中,那么它在哪?

  其实 permit 体现在 JVM 中,我们来看看在 Hotspot 中对应的源码,在 /hotspot/src/share/vm/runtime/park.hpp 中有如下一段

class Parker : public os::PlatformParker {
private:
  volatile int _counter ;
  Parker * FreeNext ;
  JavaThread * AssociatedWith ; // Current association

public:
  Parker() : PlatformParker() {
    _counter       = 0 ;
    FreeNext       = NULL ;
    AssociatedWith = NULL ;
  }
protected:
  ~Parker() { ShouldNotReachHere(); }
public:
  // For simplicity of interface with Java, all forms of park (indefinite,
  // relative, and absolute) are multiplexed into one call.
  void park(bool isAbsolute, jlong time);
  void unpark();

  // Lifecycle operators
  static Parker * Allocate (JavaThread * t) ;
  static void Release (Parker * e) ;
private:
  static Parker * volatile FreeList ;
  static volatile int ListLock ;

};

  这个 volatile int _counter 就是 permit 的底层具体实现

LockSupport 核心方法

  方法不多,如下图

  主要分两类:park 和 unpark ,我们针对这几个方法,一个一个来看,注意多看注释

  park

  会消耗 permit,若当前没有可用的 permit,则会阻塞当前线程

  park()

    方法体非常简单

    简单的一行: UNSAFE.park(false, 0L); 关于 Unsafe,有兴趣的可以去了解下:Java魔法类:Unsafe应用解析

    只看这个代码,我们很难看出什么,所幸有方法注释,简单翻译一下

    1、除非 permit 可用,否则阻塞当前线程直至 permit 可用

    2、如果 permit 可用,会将 permit 设置成 0,立即返回,不会阻塞当前线程

    3、当 permit 不可用时,当前线程会被阻塞,直至发生以下三种情况

      3.1 其他线程调用 unpark 唤醒此线程

      3.2 其他线程通过 Thread#interrupt 中断此线程

      3.3 该调用不合逻辑地(即毫无理由地)返回,可能是操作系统异常导致的

    4、park() 不会报告是什么原因导致的调用返回,有需要的话,调用者需在返回时自行检查是什么条件导致调用返回

  park(Object blocker)

    方法体也很简单

    功能与 park() 一样,只是多了个入参:Object blocker ,在线程被阻止时记录此对象,以允许监视和诊断工具识别线程被阻止的原因

    我们通过 jstack 命令,来看看 park() 和 park(Object blocker) 线程快照信息有什么区别

    示例代码:

    用 park() 时线程 t1 的快照信息如下

    用 park(Object blocker) 时线程 t1 的快照信息如下

    我们发现 park(Object blocker) 多了一行: - parking to wait for <0x000000076bbb5108> (a java.lang.String)

    当然 park(Object blocker) 不会像示例中那么使用(传个固定的字符串),传的肯定是有意义的对象,我们来看看 JDK 中哪些地方用到了它

    感兴趣的可以去看看具体的代码,其中的 this 具体是什么,它作为 blocker 有什么作用

  parkNanos(long nanos)

    nanos 表示等待的最大纳秒数;我们来翻译一下方法的注释

    1、除非 permit 可用,否则阻塞当前线程直至 permit 可用,或者等待的时间结束

    2、如果 permit 可用,会将 permit 设置成 0,立即返回,不会阻塞当前线程

    3、当 permit 不可用时,当前线程会被阻塞,直至发生以下四种情况

      3.1 其他线程调用 unpark 唤醒此线程

      3.2 其他线程通过 Thread#interrupt 中断此线程

3.3 经过指定的等待时间,不会无限期的等待下去

      3.4 该调用不合逻辑地(即毫无理由地)返回,可能是操作系统异常导致的

    4、park() 不会报告是什么原因导致的调用返回,有需要的话,调用者需在返回时自行检查是什么条件导致调用返回

    可以看出,功能与 park() 基本一致,只是多了一个等待时长

  parkNanos(Object blocker, long nanos)

    功能与 parkNanos(long nanos) 基本一样,只是多了个 Object blocker

    将 parkNanos(Object blocker, long nanos) 与 parkNanos(long nanos) 的关系与 park(Object blocker) 于 park() 的关系进行类比,就好理解了

    JDK 中有很多地方用到了 parkNanos(Object blocker, long nanos)

  parkUntil(long deadline)

    dealine 表示等待到的绝对时间,以毫秒为单位

    功能与 parkNanos(long nanos) 基本一致,只是 parkNanos(long nanos) 等待的是相对时长(纳秒),而 parkUntil(long deadline) 等待的则是绝对时间点(毫秒)

  parkUntil(Object blocker, long deadline)

    功能与 parkUntil(long deadline),只是多了个 Object blocker

    将 parkUntil(Object blocker, long deadline) 与 parkUntil(long deadline) 的关系与 parkNanos(Object blocker, long nanos) 与 parkNanos(long nanos) 的关系进行列表,就好理解了

    JDK 中有些地方用到了 parkUntil(Object blocker, long deadline)

  unpark

  方法体非常简单

  我们来翻一下它的注释

  1、使入参线程的 permit 可用(将 permit 设置成 1)

  2、如果入参线程正阻塞于 park,那么会唤醒入参线程,否则入参线程的下一次 park 不会阻塞

  3、如果入参线程还没有启动,它不会产生任何效果

  4、如果入参线程为null,它不会产生任何效果

  JDK 中有很多地方用到了它

使用场景

  因为 JDK 已经提供了丰富的 API,所以我们平时基本不会直接使用 LockSupport,所以很多人认为 LockSupport 离我们很远

  其实不然,只要我们用到 JUC 下的类来进行并发编程,那么就已经间接用到了 LockSupport 了

  JUC 中线程的阻塞与唤醒的实现,依赖的都是 LockSupport

  线程交替打印

    这是楼主之前遇到的一个面试题,LockSupport 就是其中的一个考点,具体可查看:记一个有意思的面试题 → 线程交替输出问题

    用 LockSupport 是最优的解决方式,不依赖于第三方的同步值,代码简单,逻辑清晰,非常好理解和实现

总结

  1、park 分三类,每类分两种,官方推荐用带 blocker 参数的那一种

    park()、park(Object blocker)

    parkNanos(long nanos)、parkNanos(Object blocker, long nanos)

    parkUntil(long deadline)、parkUntil(Object blocker, long deadline)

  2、park 与 unpark 之间没有严格的调用先后顺序

    permit = 1 表示可用,permit = 0 表示不可用;permit 属于线程私有

    park 消耗 permit,将 permit 从 1 设置成 0;unpark 则将 permit 设置成 1,不管设置前的值是 1 还是 0

    permit 可用,则 park 不会阻塞当前线程,将 permit 设置成 0,线程继续往下执行,否则 park 会阻塞当前线程

    unpark 会设置指定线程的 permit = 1,并唤醒指定的线程

参考

Java魔法类:Unsafe应用解析

JVM 常见线上问题 → CPU 100%、内存泄露 问题排查

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Java并发编程之LockSupport

    LockSupport是一个工具类,提供了基本的线程阻塞和唤醒功能,它是创建锁和其他同步组件的基础工具,内部是使用sun.misc.Unsafe类实现的。

    布禾
  • Java并发编程(一)Thread详解

    由上描述,可以得知线程作为cpu的基本调度单位,只有把多线程用好,才能充分利用cpu的多核资源。

    Java中文社群-磊哥
  • 《手Q Android线程死锁监控与自动化分析实践》

    一、问题背景 手Q每个版本上线以后研发同学都会收到各种问题反馈。在跟进手Q内部用户反馈的问题时,发现多例问题,其表象和原因如下: 1、问题表象:“未读不消失”、...

    腾讯Bugly
  • 《手Q Android线程死锁监控与自动化分析实践》

    手Q每个版本上线以后研发同学都会收到各种问题反馈。在跟进手Q内部用户反馈的问题时,发现多例问题,其表象和原因如下:

    腾讯Bugly
  • 【Java】【并发编程】详解并发三大特性

    在并发编程中,去解决线程安全的问题,一般可以从两大核心和三大特性作为切入点来思考怎么去解决,两大核心就是JMM内存模型和happens-before规则,三大特...

    玖柒的小窝
  • Java线程状态详解

    最近在深入研究Java并发编程,看到网上有很多关于线程状态的总结,有的不全面,有的根本就是错的。因此,在这里我结合最权威的Java源代码,尝试对Java线程状态...

    张申傲
  • 【Java】【并发编程】详解Java内存模型

    Java内存模型即 Java Menory Model,简称JMM。JMM定义了Java虚拟机(JVM)在计算机内存(RAM)中的工作方法。JVM是整个计算机虚...

    玖柒的小窝
  • 理解Java高级并发工具类LockSupport

    LockSupport是并发工具包中的一个辅助类,在直接的开发过程中很少直接使用,但是它的身影已经遍布各种工具类,众所周知,AbstractQueuedSync...

    我是攻城师
  • 【JDK1.8】JUC——LockSupport

    joemsu
  • 【JDK1.8】JUC——LockSupport

    joemsu
  • Java 编程要点之并发(Concurrency)详解

    计算机用户想当然地认为他们的系统在一个时间可以做多件事。他们认为,他们可以工作在一个字处理器,而其他应用程序在下载文件,管理打印队列和音频流。即使是单一的应用程...

    哲洛不闹
  • 《JAVA高并发编程详解》-volatile和synchronized

    朱季谦
  • Java并发编程(一)

    昨天忘关电脑了,今天极其极其慢,开了没几个应用,一卡一卡,同步打开几个笔记都卡到不能加载,平时上网课吧,小伙伴说不系统,那根据网课的大纲白*自己找资料不香么...

    疯狂的KK
  • 突击并发编程JUC系列-Locksupport 与 Condition

    JDK 中的 rt.jar 包里面的 LockSupport 是个工具类,当需要阻塞或唤醒一个线程的时候,都可以使用 LockSupport 工具类来完成相应工...

    山间木匠
  • 【原创】Java并发编程系列13 | LookSupport

    java.util.concurrent 中源码频繁使用的 LockSupport 来阻塞线程和唤醒线程,如 AQS 的底层实现用到 LockSupport.p...

    java进阶架构师
  • Java并发编程:线程封闭和ThreadLocal详解

    什么是线程封闭 当访问共享变量时,往往需要加锁来保证数据同步。一种避免使用同步的方式就是不共享数据。如果仅在单线程中访问数据,就不需要同步了。这种技术称为线程封...

    方志朋
  • Java并发编程(2)- 线程安全性详解

    说到原子性,就不得不提及JDK里的atomic包,该包中提供了很多Atomic的类,本小节主要介绍该包中常用的几个类。这些类都是通过CAS来实现原子性的,ato...

    端碗吹水
  • 为什么说LockSupport是Java并发的基石?

    而AQS中的控制线程又是通过LockSupport类来实现的,因此可以说,LockSupport是Java并发基础组件中的基础组件。LockSupport定义了...

    luoxn28
  • 了解Java并发编程基础!超详细!

    Java程序天生就是多线程程序,因为执行main()方法的是一个名称为main的线程。下面使用JMX来查看一个普通的Java程序包含哪些线程,代码如下。

    程序员的时光001

扫码关注云+社区

领取腾讯云代金券