zl程序教程

您现在的位置是:首页 >  其他

当前栏目

Unsafe类park和unpark方法源码深入分析(mutex+cond)

2023-03-15 22:00:55 时间

说明:本篇博客整理自文末的多篇参考博客(每篇博客各有侧重)。本文结合源码对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、https://www.cnblogs.com/qingquanzi/p/8228422.html 自己动手写把锁--LockSupport深入浅出

2、https://blog.csdn.net/a7980718/article/details/83661613 jdk1.8 Unsafe类 park和unpark方法解析

3、https://cloud.tencent.com/developer/article/1198495 Synchronized 和 Lock 锁在JVM中的实现原理以及代码解

4、http://blog.sina.com.cn/s/blog_967817f20101bsf0.html pthread条件变量condition配合mutex锁使用

5、https://code.woboq.org/userspace/glibc/nptl/pthread_cond_wait.c.html glibc/nptl/pthread_cond_wait.c源码

6、https://www.zhihu.com/question/24116967 pthread_cond_wait 为什么需要传递 mutex 参数

7、https://www.cnblogs.com/gdk-0078/p/5172941.html Linux中阻塞队列及等待机制

8、https://www.cnblogs.com/HadesBlog/p/13170298.html pthread_mutex_lock源码分析

9、https://zhuanlan.zhihu.com/p/107196906 操作系统中多线程的个人理解:mutex和condition

10、https://www.dazhuanlan.com/2020/01/02/5e0d9b848a3b7/  hotspot Thread JavaThread OSThread

11、https://www.cnblogs.com/linzhanfly/p/11258496.html Thread.interrrupt()源码跟踪

12、https://blog.csdn.net/qq_31865983/article/details/105184585 Hotspot Parker和ParkEvent 源码解析

13、https://blog.csdn.net/qq_31865983/article/details/105174567  Hotspot Thread本地方法实现 源码解析

14、https://www.cnblogs.com/twoheads/p/10150063.html  jvm简介:偏向锁、轻量级锁和重量级锁

15、https://www.jianshu.com/p/09de11d71ef8 死磕Synchronized底层实现 重量级锁