转载:https://blog.csdn.net/saintyyu/article/details/107426428
说明:本篇博客整理自文末的多篇参考博客(每篇博客各有侧重)。本文结合源码对Unsafe的park和unpark方法进行了完整全面的梳理,并对部分参考博客中存在的错误描述进行说明。
LockSupport类的park/unpark方法可以更简单灵活地实现synchronized关键字 + Object类的wait/nofity方法所达到的让线程按照指定顺序执行的效果(详见参考博客1),而LockSupport底层就是通过调用Unsafe类的park和unpark方法来实现的。
由参考博客2可知(建议先读完参考博客2),每个Thread都包含一个Parker成员变量,Unsafe的park/unpark方法最终就是调用Parker类的park/unpark方法。在Parker的park方法中,会调用pthread_mutex_trylock方法,该方法实际上是pthread_mutex_lock方法的非阻塞版本。也就是说,不同于pthread_mutex_lock方法在获取不到互斥锁时会阻塞住,调用pthread_mutex_trylock方法不会阻塞当前线程,而是立即返回一个值来描述互斥锁是否获取成功(参考博客3中说LockSupport和synchronized一样,都是通过调用pthread_mutex_lock方法来阻塞当前线程,这个说法是不对的,实际上synchronized确实是通过调用pthread_mutex_lock方法来阻塞当前线程,而LockSupport是通过调用atomic_load_acquire方法阻塞等待唤醒信号,后面会详细介绍)。
既然LockSupport没有调用pthread_mutex_lock方法,那么LockSupport的park方法到底是阻塞在什么方法上呢?由参考博客2可知,答案是pthread_cond_wait方法。遗憾的是参考博客2并未给出pthread_cond_wait方法的具体实现。这里先给出一张park和unpark底层的实现时序图:
由图可知,pthread_cond_wait方法会先操作条件变量,然后释放锁,接着阻塞当前线程,等待condition的唤醒信号。这里之所以要释放锁,是为了让当前的阻塞线程和唤醒线程互斥地访问并操作条件变量(该图中调用pthread_cond_signal的线程在调用该方法之前会先修改条件变量,图中未画出),否则就可能会出现唤醒消息丢失(详见参考博客6)。
当唤醒线程修改了条件变量、执行完pthread_cond_signal方法,并释放锁之后,当前被阻塞的线程从阻塞状态恢复到执行状态,此时会重新竞争互斥锁,竞争到互斥锁之后会再次修改条件变量(修改_counter等变量,就是为了标记锁的占用情况,详见参考博客2中的源码注释)。
为了进一步弄清楚pthread_cond_wait方法的是如何阻塞的,我阅读了pthread_cond_wait的源码(参考博客5),核心流程梳理如下(因为__pthread_cond_wait调用了__pthread_cond_wait_common,所以重点看后者,以下只截取核心思路):
//1、条件变量入等待队列
uint64_t wseq = __condvar_fetch_add_wseq_acquire (cond, 2);
//2、释放互斥锁
err = __pthread_mutex_unlock_usercnt (mutex, 0);
//3、阻塞并等待唤醒信号(实际可能得到关闭信号)
unsigned int signals = atomic_load_acquire (cond->__data.__g_signals + g);
//4、先循环等待maxspin次,获取signal
unsigned int spin = maxspin;
while (signals == 0 && spin > 0)
{
/* Check that we are not spinning on a group that's already
closed. */
if (seq < (__condvar_load_g1_start_relaxed (cond) >> 1))
goto done;
/* TODO Back off. */
/* Reload signals. See above for MO. */
signals = atomic_load_acquire (cond->__data.__g_signals + g);
spin--;
}
//5、阻塞获取互斥锁
err = futex_wait_cancelable (
cond->__data.__g_signals + g, 0, private);
//6、获取互斥锁后执行条件变量修改操作
......
由此可见,上面的核心源码和上面的示意图是相匹配的。
最后,详细阅读参考博客6中的源码,结合参考博客7可知,阻塞机制底层是Linux内核基于等待队列wait_queue和等待事件wait_event来实现的。
参考博客:
1、自己动手写把”锁”---LockSupport深入浅出 - 清泉^_^ - 博客园 自己动手写把锁--LockSupport深入浅出
2、jdk1.8 Unsafe类 park和unpark方法解析_lcjmsr的博客-CSDN博客_unsafe的park jdk1.8 Unsafe类 park和unpark方法解析
3、Synchronized 和 Lock 锁在JVM中的实现原理以及代码解析 - 云+社区 - 腾讯云 Synchronized 和 Lock 锁在JVM中的实现原理以及代码解
4、pthread条件变量condition(配合mutex锁使用),经典,有图_OpenWrt_新浪博客 pthread条件变量condition配合mutex锁使用
5、pthread_cond_wait.c source code [glibc/nptl/pthread_cond_wait.c] - Woboq Code Browser glibc/nptl/pthread_cond_wait.c源码
6、pthread_cond_wait 为什么需要传递 mutex 参数? - 知乎 pthread_cond_wait 为什么需要传递 mutex 参数
7、linux中的阻塞机制及等待队列 - touchcode - 博客园 Linux中阻塞队列及等待机制
8、pthread_mutex_lock源码分析 - wa小怪兽 - 博客园 pthread_mutex_lock源码分析
9、操作系统中多线程的个人理解:mutex和condition - 知乎 操作系统中多线程的个人理解:mutex和condition
10、https://www.dazhuanlan.com/2020/01/02/5e0d9b848a3b7/ hotspot Thread JavaThread OSThread
11、Thread.interrupt()源码跟踪 - 月下小魔王 - 博客园 Thread.interrrupt()源码跟踪
12、Hotspot Parker和ParkEvent 源码解析_孙大圣666的博客-CSDN博客 Hotspot Parker和ParkEvent 源码解析
13、Hotspot Thread本地方法实现 源码解析_孙大圣666的博客-CSDN博客 Hotspot Thread本地方法实现 源码解析
14、JVM锁简介:偏向锁、轻量级锁和重量级锁 - twoheads - 博客园 jvm简介:偏向锁、轻量级锁和重量级锁
15、死磕Synchronized底层实现--重量级锁 - 简书 死磕Synchronized底层实现 重量级锁