https://blog.csdn.net/u013978512/article/details/120011860?spm=1001.2014.3001.5501 这篇文章我们讲了AQS的实现过程,其中线程的阻塞和唤醒是通过LockSupport的park和unpark实现的,结尾抛出了个问题:唤醒在阻塞之前,会出现什么情况。答案是unpark可以在park之前,这样,被park的线程就不会阻塞,为了查看其中的缘由,发现 https://www.cnblogs.com/yonghengzh/p/14280670.html 这篇文章从JVM源码讲解,清晰透彻,这里进行转载(侵权可删,大家可直接查看原文,本文为以防原文章丢失)。

前言

熟悉 Java 并发包的人一定对 LockSupport 的 park/unpark 方法不会感到陌生,它是 Lock(AQS)的基石,给 Lock(AQS)提供了挂起/恢复当前线程的能力。

LockSupport 的 park/unpark 方法本质上是对 Unsafe 的 park/unpark 方法的简单封装,而后者是 native 方法,对 Java 程序来说是一个黑箱操作,那么要想了解它的底层实现,就必须深入 Java 虚拟机的源码。

本篇将介绍 park/unpark 方法在 Hotsport 虚拟机中的具体实现。

Parker 源码调试与分析

在 Hotspot 源码中,unsafe.cpp 文件专门用于为 Java Unsafe 类中的各种 native 方法提供具体实现。

其中 park 方法的实现代码如下:

unpark 方法的实现代码如下:

两者的核心操作都是通过委托当前线程所关联的 Parker 对象来完成的(每个线程都会关联一个自己的 Parker 对象),于是,Parker 对象的 park/unpark 方法就成为了我们的焦点。

下面我将联合 Java 程序与 Hotspot 源码一起调试,观察 Parker 对象的 park/unpark 方法的内部操作。

其中 Java 程序的代码如下:

public static void main(String[] args) {Thread t1 = new Thread(() -> {System.out.println("park开始");LockSupport.park();System.out.println("park结束");}, "t1");Thread t2 = new Thread(() -> {System.out.println("unpark开始");LockSupport.unpark(t1);System.out.println("unpark结束");}, "t2");Scanner scanner = new Scanner(System.in);String input;System.out.println("输入“1”启动t1线程,输入“2”启动t2线程,输入“quit”退出");while (!(input = scanner.nextLine()).equals("quit")) {if (input.equals("1")) {if (t1.getState().equals(Thread.State.NEW)) {t1.start();}} else if (input.equals("2")) {if (t2.getState().equals(Thread.State.NEW)) {t2.start();}}}
}

我们采用远程调试的方式运行上面的 Java 程序,然后通过在控制台输入“1” 来启动 t1 线程。当 t1 线程启动后,LockSupport.park 方法就会得以执行。

如图所示,当前 t1 线程停在了断点处,即停在了 Parker::park 方法的第一条语句上。

我们来分析一下该方法主要做的事情。

它首先利用一个原子交换操作将计数器的值改为 0,同时检查计数器的原值是否大于 0,如果大于 0,表示当前 Parker 对象的 unpark 方法先于 park 方法执行了(因为 unpark 方法会把计数器的值改为 1),那么本次 park 方法将直接返回,表示取消本次操作。如果计数器的原值不大于 0,则继续往下执行。

接着判断当前线程是否被标记了中断,如果是的话就直接返回,否则就通过 pthread_mutex_trylock 函数尝试加 mutex 锁,如果加锁失败也直接返回。(pthread_mutex_trylock 函数是一个系统调用,它会针对操作系统的一个互斥量进行加锁,加锁成功将返回 0)。

在我们的调试中,以上所有条件判断都不命中,于是线程顺利地执行到了下图所示的位置。

图中断点处的代码相当关键,它完成了对 pthread_cond_wait 函数的调用,该函数是 Linux 标准线程库(libpthread.so)中的一个系统调用,它会使当前线程加入操作系统的条件等待队列,同时释放 mutex 锁并使当前线程挂起。

Java 中的 wait 和 await 方法提供了和 pthread_cond_wait 函数同样的功能,前者本质上是对后者的封装。如果对 pthread_cond_wait 函数的具体实现感兴趣,可以参考: https://code.woboq.org/userspace/glibc/nptl/pthread_cond_wait.c.html

由于 pthread_cond_wait 函数会使当前线程挂起,所以在我点击 "Step Over" 之后,线程阻塞在了 pthread_cond_wait 函数上,并等待被唤醒。

下图显示了通过 jstack 命令打印的线程堆栈信息,可以看到 t1 线程已经处于 waiting (parking) 状态。

至此,park 操作暂时告一段落。

接下来,我们通过在控制台输入“2” 来启动 t2 线程。当 t2 线程启动后,LockSupport.unpark(t1) 就会得以执行。

如图所示,当前 t2 线程停在了断点处,即停在了 Parker::unpark 方法的第二行代码上。

该方法做的事情相对简单,它先是给当前线程加锁,然后将计数器的值改为 1,接着判断 Parker 对象所关联的线程是否被 park,如果是,则通过 pthread_mutex_signal 函数唤醒该线程,最后释放锁。

pthread_mutex_signal 函数通常与 pthread_cond_wait 函数配套使用,其作用是唤醒操作系统中在某个条件变量上等待着的线程。

当 unpark 操作完成后,之前被 park 的线程将恢复至运行状态(需要先拿到 mutex 锁),然后从 pthread_cond_wait 方法中返回,接着执行剩余代码。下图显示了Parker::park 方法的剩余代码。

可以看到,当线程恢复运行后,计数器的值会再次被置为 0,然后线程会释放锁,并结束整个 park 操作。

park/unpark 原理总结

每个线程都会关联一个 Parker 对象,每个 Parker 对象都各自维护了三个角色:计数器、互斥量、条件变量。

park 操作:

  1. 获取当前线程关联的 Parker 对象。

  2. 将计数器置为 0,同时检查计数器的原值是否为 1,如果是则放弃后续操作。

  3. 在互斥量上加锁。

  4. 在条件变量上阻塞,同时释放锁并等待被其他线程唤醒,当被唤醒后,将重新获取锁。

  5. 当线程恢复至运行状态后,将计数器的值再次置为 0。

  6. 释放锁。

unpark 操作:

  1. 获取目标线程关联的 Parker 对象(注意目标线程不是当前线程)。

  2. 在互斥量上加锁。

  3. 将计数器置为 1。

  4. 唤醒在条件变量上等待着的线程。

  5. 释放锁。

补充:jstack 命令和 kill 命令

jstack 命令会给 Java 虚拟机进程发送一个 SIGQUIT 信号,当 Java 虚拟机收到信号后,会另起一个线程专门执行打印线程堆栈的任务。如图,从 GDB 标签页中可以观察到 SIGQUIT 信号。

在 Linux 中使用 kill -3 命令也可以实现和 jstack 命令几乎一样的效果,这是因为 kill 命令本身就是一个用于给进程发送信号的工具,只不过默认发送的是 SIGTERM 信号(终止信号),该信号用于终止一个进程。可以通过 kill -l 命令查看所有可用信号,kill -3 表示发送 SIGQUIT 信号。

LockSupport的park/unpark分析相关推荐

  1. LockSupport的park和unpark

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

  2. java park unpark_LockSupport(park/unpark)源码分析

    concurrent包是基于AQS (AbstractQueuedSynchronizer)框架的,AQS框架借助于两个类: Unsafe(提供CAS操作) LockSupport(提供park/un ...

  3. 并发编程之LockSupport的 park 方法及线程中断响应

    系列文章目录 Java并发编程技术知识点梳理(第一篇)操作系统底层工作的整体认识 Java并发编程技术知识点梳理(第二篇)并发编程之JMM&volatile详解 Java并发编程技术知识点梳理 ...

  4. java lock park_java并发编程-LockSupport中park与unpark基本使用与原理简单分析

    文章目录 java并发编程原理之---park与unpark 基本使用 情况一,先park再unpark,代码举例与分析 情况二,先unpark再park,代码举例与分析 特点 原理之park &am ...

  5. Java的LockSupport.park()实现分析(转载)

    LockSupport类是Java6(JSR166-JUC)引入的一个类,提供了基本的线程同步原语.LockSupport实际上是调用了Unsafe类里的函数,归结到Unsafe里,只有两个函数: 1 ...

  6. LockSupport 以及 park、unpark 方法

    一.LockSupport 是 jsr 166 中新增的 juc 工具类. LockSupport 类主要用于创建锁和其他同步类来实现线程阻塞. 这个类与他使用的每个线程进行关联, 如果可用就立即 p ...

  7. Java LockSupport以及park、unpark方法源码深度解析

    介绍了JUC中的LockSupport阻塞工具以及park.unpark方法的底层原理,从Java层面深入至JVM层面. 文章目录 1 LockSupport的概述 2 LockSupport的特征和 ...

  8. Java的LockSupport.park()实现分析

    LockSupport类是Java6(JSR166-JUC)引入的一个类,提供了基本的线程同步原语.LockSupport实际上是调用了Unsafe类里的函数,归结到Unsafe里,只有两个函数: p ...

  9. LockSupport 的 park 和 unpark 以及线程中断对 park 的影响

    park() Thread t1 = new Thread(() -> {System.out.println("t1 park");LockSupport.park(); ...

最新文章

  1. Bootstrap树控件(Tree控件组件)使用经验分享
  2. 【NOI2016】优秀的拆分(后缀数组)
  3. mysql的limit使用方法
  4. Redis专题-缓存穿透、缓存雪崩、缓存击穿
  5. 从零入门 Serverless | 一文搞懂函数计算及其工作原理
  6. 学习使用bilstm_crf_model出现的bug
  7. seaborn 子图_Seaborn FacetGrid:进一步完善子图
  8. C++工作笔记-简单工厂模式基础(用静态类传入函数指针,再进行调用)(仿大佬代码)
  9. 一个传统媒体人转型创业的真实故事
  10. ORACLE 11g安装图解
  11. 欧盟批准ATT收购时代华纳 或年底前完成交易
  12. E2: A Framework for NFV Applications, SOSP' 15
  13. 如何基于数据快速构建用户模型(Persona)?
  14. window python环境搭建_Python入门-环境搭建详解(Window平台)
  15. 【USACO 4.3.2】质数方阵
  16. rpm、dpkg、yum、apt比较
  17. OJ每日一练——细菌个数
  18. 代码详解设计模式--中介者模式
  19. 终于,字节跳动要取消大小周了,我 1.7 万人的票圈都快炸了!
  20. QQ头像变灰算法[图]

热门文章

  1. 【Proteus仿真】基于74LS148+74LS279+74LS48的四路抢答器
  2. Hadoop MapReduce 统计汽车销售信息
  3. 文件传输助手——自同步、使用方法
  4. [C语言] 字符串的输出格式
  5. python和revit_Python 與 Revit
  6. 在其他电脑远程访问svn
  7. 如何用PPT编制方案 (3)PPT的页面规划设计
  8. STM32-OLED屏幕显示教程
  9. 创建第一个适用于Android的自定义Gradle插件-第2部分:在构建时生成资源
  10. 定义c语言字符串的三种方法