我最近建立了一个在线自习室(App:番茄ToDO)用于相互监督学习,感兴趣的小伙伴可以加入。自习室加入码:D5A7A644

点击上方“后端开发技术”,选择“设为星标” ,优质资源及时送达

Java 并发包下的类基本都是基于AQS(AbstractQueuedSynchronizer)框架实现的,而 AQS 线程安全的实现又是基于两个很关键的类 Unsafe 和 LockSupport。

其中 Unsafe 主要直接提供 CAS 操作,LockSupport 主要提供 park/unpark 操作,而 park/unpark 最终调用还是 Unsafe 类,所以 Unsafe 类才是关键。

如果在面试中你能从 JVM 源码的角度解读其原理,定会给面试官留下不一样的印象。下面我们开始看源码。

(如果不会下载JVM源码可以后台回复 “jdk”,获得下载压缩包)

LockSupport 类源码:

public static void park() {UNSAFE.park(false, 0L);
}
public static void unpark(Thread thread) {if (thread != null)UNSAFE.unpark(thread);
}

Unsafe 类源码:

//park
public native void park(boolean isAbsolute, long time);//unpack
public native void unpark(Object var1);

由代码可见,Unsafe 类的 park/unpark 是 native 级别的实现。使用 native 关键字说明这个方法是原生函数,也就是这个方法是用 C/C++ 语言实现的,并且被编译成了 DLL,由 Java 去调用。

park 函数作用是将当前调用线程阻塞,unpark 函数则是将指定线程线程唤醒。

park 和 unpark 作用

park 是等待一个许可,unpark 是为某线程提供一个许可。如果某线程A调用 park,那么除非另外一个线程调用 unpark(A) 给A一个许可,否则线程 A 将阻塞在 park 操作上。每次调用一次 park,需要有一个 unpark来解锁。

并且 unpark 可以先于 park 调用,但是不管 unpark 先调用几次,都只提供一个许可,不可叠加,只需要一次 park 来消费掉 unpark 带来的许可,再次调用会阻塞。

Unsafe.park 源码

在 Linux 系统下,park 和 unpark 是用的 Posix 线程库 pthread 中的 mutex(互斥量),condition(条件变量)来实现的。

简单来说,mutex 和 condition 保护了一个叫 _counter 的信号量。当 park 时,这个变量被设置为 0,当 unpark 时,这个变量被设置为1。当 _counter=0 时线程阻塞,当 _counter > 0 直接设为 0 并返回。

每个 Java 线程都有一个 Parker 实例,Parker 类部分源码如下:

class Parker : public os::PlatformParker {
private:  volatile int _counter ;  ...
public:  void park(bool isAbsolute, jlong time);  void unpark();  ...
}
class PlatformParker : public CHeapObj<mtInternal> {  protected:  pthread_mutex_t _mutex [1] ;  pthread_cond_t  _cond  [1] ;  ...
}

由源码可知 Parker 类继承于 PlatformParker,实际上是用 Posix 的 mutex,condition 来实现的。Parker 类里的 _counter 字段,就是用来记录 park 和 unpark 是否需要阻塞的标识。

执行过程

具体的执行逻辑已经用注释标记在代码中,简要来说,就是检查 _counter 是不是大于 0,如果是,则把 _counter 设置为 0,返回。如果等于零,继续执行,阻塞等待。

具体请看源码中注释。

void Parker::park(bool isAbsolute, jlong time) {//判断信号量counter是否大于0,如果大于设为0返回if (Atomic::xchg(0, &_counter) > 0) return;//获取当前线程Thread* thread = Thread::current();assert(thread->is_Java_thread(), "Must be JavaThread");JavaThread *jt = (JavaThread *)thread;//如果中途已经是interrupt了,那么立刻返回,不阻塞// Check interrupt before trying to waitif (Thread::is_interrupted(thread, false)) {return;}//记录当前绝对时间戳// Next, demultiplex/decode time argumentstimespec absTime;//如果park的超时时间已到,则返回if (time < 0 || (isAbsolute && time == 0) ) { // don't wait at allreturn;}//更换时间戳if (time > 0) {unpackTime(&absTime, isAbsolute, time);}// Enter safepoint region// Beware of deadlocks such as 6317397.// The per-thread Parker:: mutex is a classic leaf-lock.// In particular a thread must never block on the Threads_lock while// holding the Parker:: mutex.  If safepoints are pending both the// the ThreadBlockInVM() CTOR and DTOR may grab Threads_lock.//进入安全点,利用该thread构造一个ThreadBlockInVMThreadBlockInVM tbivm(jt);// Don't wait if cannot get lock since interference arises from// unblocking.  Also. check interrupt before trying waitif (Thread::is_interrupted(thread, false) || pthread_mutex_trylock(_mutex) != 0) {return;}//记录等待状态int status ;//中途再次检查许可,有则直接返回不等带。if (_counter > 0)  { // no wait needed_counter = 0;status = pthread_mutex_unlock(_mutex);assert (status == 0, "invariant") ;// Paranoia to ensure our locked and lock-free paths interact// correctly with each other and Java-level accesses.OrderAccess::fence();return;}OSThreadWaitState osts(thread->osthread(), false /* not Object.wait() */);jt->set_suspend_equivalent();// cleared by handle_special_suspend_equivalent_condition() or java_suspend_self()assert(_cur_index == -1, "invariant");if (time == 0) {_cur_index = REL_INDEX; // arbitrary choice when not timed//线程条件等待 线程等待信号触发,如果没有信号触发,无限期等待下去。status = pthread_cond_wait (&_cond[_cur_index], _mutex) ;} else {_cur_index = isAbsolute ? ABS_INDEX : REL_INDEX;//线程等待一定的时间,如果超时或有信号触发,线程唤醒status = os::Linux::safe_cond_timedwait (&_cond[_cur_index], _mutex, &absTime) ;if (status != 0 && WorkAroundNPTLTimedWaitHang) {pthread_cond_destroy (&_cond[_cur_index]) ;pthread_cond_init    (&_cond[_cur_index], isAbsolute ? NULL : os::Linux::condAttr());}}_cur_index = -1;assert_status(status == 0 || status == EINTR ||status == ETIME || status == ETIMEDOUT,status, "cond_timedwait");_counter = 0 ;status = pthread_mutex_unlock(_mutex) ;assert_status(status == 0, status, "invariant") ;// Paranoia to ensure our locked and lock-free paths interact// correctly with each other and Java-level accesses.OrderAccess::fence();// If externally suspended while waiting, re-suspendif (jt->handle_special_suspend_equivalent_condition()) {jt->java_suspend_self();}
}

Unsafe.unpark源码

unpark 直接设置 _counter 为 1,再 unlock mutex 返回。如果 _counter 之前的值是 0,则还要调用 pthread_cond_signal 唤醒在 park 中等待的线程。源码如下,已做详细注释:

void Parker::unpark() {//定义两个变量,staus用于判断是否获取锁int s, status ;//获取锁status = pthread_mutex_lock(_mutex);//判断是否成功assert (status == 0, "invariant") ;//存储原先变量_counters = _counter;//把_counter设为1_counter = 1;if (s < 1) {// thread might be parkedif (_cur_index != -1) {// thread is definitely parkedif (WorkAroundNPTLTimedWaitHang) {status = pthread_cond_signal (&_cond[_cur_index]);assert (status == 0, "invariant");status = pthread_mutex_unlock(_mutex);assert (status == 0, "invariant");} else {status = pthread_mutex_unlock(_mutex);assert (status == 0, "invariant");status = pthread_cond_signal (&_cond[_cur_index]);assert (status == 0, "invariant");}} else {//释放锁pthread_mutex_unlock(_mutex);assert (status == 0, "invariant") ;}} else {//释放锁pthread_mutex_unlock(_mutex);assert (status == 0, "invariant") ;}
}

(如果不会下载JVM源码可以后台回复 “jdk”,获得下载压缩包)

如果觉得对你有帮助,欢迎点赞、标

从HotSpot源码,深度解读 park 和 unpark |原创相关推荐

  1. 【源码阅读】Java集合之一 - ArrayList源码深度解读

    Java 源码阅读的第一步是Collection框架源码,这也是面试基础中的基础: 针对Collection的源码阅读写一个系列的文章,从ArrayList开始第一篇. ---@pdai JDK版本 ...

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

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

  3. Go netpoll I/O 多路复用构建原生网络模型之源码深度解析

    原文 Go netpoll I/O 多路复用构建原生网络模型之源码深度解析 导言 Go 基于 I/O multiplexing 和 goroutine 构建了一个简洁而高性能的原生网络模型(基于 Go ...

  4. vue源码深入解读MVVM(视图模板引擎),你真的了解双向绑定(v-model),数据劫持(observe),发布订阅模式吗?带你手鲁mvvm引擎。源码奉上(详细注释)!

    文章目录 #1.vue的强大之处不必细说,vue的核心v-model的实现原理,网上都有很多.但是真正自己实现双向绑定,mvvm的源码却几乎没见过. #1.2本人根据源码的解读,理解,以及借鉴网上的视 ...

  5. Spring源码深度解析(郝佳)-Spring 常用注解使用及源码解析

      我们在看Spring Boot源码时,经常会看到一些配置类中使用了注解,本身配置类的逻辑就比较复杂了,再加上一些注解在里面,让我们阅读源码更加难解释了,因此,这篇博客主要对配置类上的一些注解的使用 ...

  6. AFL(American Fuzzy Lop)源码详细解读(1)

    AFL(American Fuzzy Lop)源码详细解读(1) 多亏大佬们的文章,对读源码帮助很大: https://eternalsakura13.com/2020/08/23/afl/ http ...

  7. AFL(American Fuzzy Lop)源码详细解读(3)

    AFL(American Fuzzy Lop)源码详细解读(3) 本篇是关于主循环阶段的内容,整个AFL最核心的部分,篇幅较长.最后简述一下afl_fuzz整体流程. 多亏大佬们的文章,对读源码帮助很 ...

  8. 双管齐下,JDK源码+HotSpot源码一次性学完

    JDK源码手册 除了第一章节的内容外,我们会从第二章开始自下而上,从简单到复杂的有顺序的深度学习整个Concurrent包! ​ Semaphore(Semaphore也就是信号量,提供了资源数量的并 ...

  9. libevent源码深度剖析

    原文地址:http://blog.csdn.net/sparkliang/article/details/4957667 libevent源码深度剖析一 --序幕 张亮 1 前言 Libevent是一 ...

  10. libevent源码深度剖析十一

    libevent源码深度剖析十一 --时间管理 张亮 为了支持定时器,Libevent必须和系统时间打交道,这一部分的内容也比较简单,主要涉及到时间的加减辅助函数.时间缓存.时间校正和定时器堆的时间值 ...

最新文章

  1. 学习网页制作中如何在正确选取和使用 CSS 单位
  2. python中常用的模块二
  3. CF204E-Little Elephant and Strings【广义SAM,线段树合并】
  4. 循环队列 - 顺序存储结构
  5. itsdangerous
  6. nginx 四种策略
  7. 基于bootsplash的嵌入式linux启动画面定制
  8. java加解密算法概述
  9. EL表达式和JSTL标签库学习总结
  10. 拉格朗日法建立动力学方程
  11. Ricequant-估值因子的分析
  12. JavaScript数组some方法
  13. Error staring Tomcat Cannot connect to VM错误解决办法
  14. 国内外好的OKR管理工具有哪些
  15. 《指数基金投资指南》 阅读笔记
  16. RabbitMQ学习笔记:集群和网络分区(Network Partitions)
  17. 2007热点技术职位排行及点析
  18. 【Python】【setFocus】焦点
  19. 获取手机设备型号、系统版本、手机型号等信息
  20. Microsoft SQL Server 2000 简体中文企业版+SP4升级补丁

热门文章

  1. 天津大学仁爱学院ACM工作室介绍
  2. 程序员除了去上班还能怎样赚钱?
  3. 分享一款上班摸鱼神器,再也不怕领导突然出现在身后了~
  4. 最保险的“跳槽理由”
  5. justinmind破解方法
  6. 【笔记】【数字逻辑】可能是最详细的二进制、八进制、十进制、十六进制进制转换笔记
  7. 小白:测试的逻辑思维
  8. 对数及对比度拉伸变换
  9. 英语中名词复数读法(清辅音、浊辅音)
  10. Fantastic Graph 2018沈阳网络赛