程序地带

JVM 源码分析(四):深入理解 park / unpark


前言Parker 源码调试与分析park/unpark 原理总结补充:jstack 命令和 kill 命令


前言

熟悉 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 操作:


获取当前线程关联的 Parker 对象。将计数器置为 0,同时检查计数器的原值是否为 1,如果是则放弃后续操作。在互斥量上加锁。在条件变量上阻塞,同时释放锁并等待被其他线程唤醒,当被唤醒后,将重新获取锁。当线程恢复至运行状态后,将计数器的值再次置为 0。释放锁。

unpark 操作:


获取目标线程关联的 Parker 对象(注意目标线程不是当前线程)。在互斥量上加锁。将计数器置为 1。唤醒在条件变量上等待着的线程。释放锁。
补充:jstack 命令和 kill 命令

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



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


版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/yonghengzh/p/14280670.html

随机推荐

上市公司信用评级模型(因子分析法)

1.引言信用统计评级模型是指通过观察和分析企业财务报表中的一些感性的财务来判别企业财务状况以及采取适当防范施的数学模型对于投资和债权,可以根据财务预测指数的动态分析进行正确的投资选择&#...

radar_sun 阅读(564)

加密技术在企业数据安全中的应用.

加密技术在企业数据安全中的应用.

    随着大型企业管理软件的发展,其应用越来越广泛,企业数据平台涉及局域网、广域网、Internet等,在各类系统中保存的企业关键数据量也越来越大ÿ...

u014789708 阅读(699)

xml格式美化打印

经常遇到知道一个功能是可以实现的,找不到现成的函数,在网上找了半天,试了无数个方法才终于找到。记录一下importxml.dom.minidomfile...

概概不迷茫 阅读(443)

PTA天梯赛——谷歌的招聘C++

PTA天梯赛——谷歌的招聘C++

一,分析题目从任一给定的长度为L的数字中,找出最早出现的K位连续数字所组成的素数。题目说的蛮清楚的,素数就是除1和本身以外都除不尽。二,思路寻找...

姜晓虎 阅读(542)

Mqtt实现发布订阅-简单案例

Mqtt实现发布订阅-简单案例当你看到这边文章是想必你也是刚开始学习mqtt,也许你对于什么是mqtt还不是太了解,建议去把mqtt的入门介绍(点击跳转)看一下࿰...

墨尘-程序员 阅读(589)

直播动画框架探索

目录直播动画框架探索方案对比动画库对比直播动画框架探索方案对比方案优点缺点帧动画OpenGL实现简单播放图片资源占用高;属性动画资源占用小1.播放图片资源占用高;2.实现难...

gavin_Zippo 阅读(112)

关于变量和函数的提升测试题

//vara=1;//functionfn(a){///*参数a重新定义了函数内的a函数内部的a无法改变全局变量a的值*////*vara=1*///console.log(a);/*...

JessicaLilyAn 阅读(393)