说明:本篇博客整理自文末的多篇参考博客(每篇博客各有侧重)。本文结合源码对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底层实现 重量级锁

16、 Linux 互斥锁、原子操作实现原理_kmcfly的博客-CSDN博客_互斥锁的实现原理

public static void park (Object blocker) { Thread t = Thread.currentThread(); setBlocker(t, blocker); UNSAFE . park (false, 0L); setBlocker(t, nu..
park Unsafe 里的native 方法 ,LockSupport 通过调用 Unsafe park unpark 提供了几个操作。 Unsafe park 方法 如下: public native void park (boolean isAbsolute, long time);  第一个参数是是否是绝对时间,第二个参数是等待时间值。如果isAbsolute是true则会实现ms定时。如果i...
LockSupport 和 CAS 是 Java 并发包中很多并发工具控制机制的基础,它们底层其实都是依赖 Unsafe 实现 为什么使用LockSupport 如果只是LockSupport在使用起来比Object的wait/notify简单, 那还真没必要专门讲解下LockSupport。最主要的是灵活性。 上边的例子代码中,主线程调用了Thread.sleep(1000) 方法 来等待线程A计算完成进入wait状态。如果去掉Thread.sleep()调用,代码如下: public class TestObjW
park unpark park unpark 是LockSupport里面的 方法 park 的中文翻译有停在这的意思,所以 park 方法 的作用就是停下一个线程,而 unpark 就是唤醒线程。 与Object的wait&notify相比 wait和notify相比必须配合Object Monitor一起使用,而 park unpark 则不用 park & unpark 是以线程为单位来阻塞和唤醒线程的,而notify只能随机唤醒一个,notifyAll只能唤醒所有,不怎么精确 park (Object blocker) public static void park (Object blocker) { Thread t = Thread.currentThread(); setBlocker(t, blocker); UNSAFE . park (false, 0
目录概览LockSupport解析用法:具体使用:原理概述: 源码 解析:底层原理: park 原理流程图: unpark 原理流程图: park () 源码 unpark () 源码 :Q & A1. unpark 是否可以调用多次,以获取多个permit?2. unpark 是否一定要在 park 之后调用?3. park 会像wait()一样睡眠释放当前 吗? LockSupport解析 LockSupport 底层使用Posix线程库pthreads的系统级别 互斥量 mutex cond ition,所以它的系统消耗是比
一、LockSupport LockSupport 工具可以帮助我们阻塞或唤醒一个线程,也是构建同步组件的基础工具。 AQS中 对于节点的阻塞和唤醒就是通过LockSupport的 park unpark 实现的。 LockSupport 与 对象的 wait/n...
Unsafe Java 中提供的一个可以绕过 Java 安全检查的 ,它提供了直接操作内存和执行非法操作等功能。通过使用 Unsafe ,我们可以访问某些私有变量和 方法 。 访问私有变量: Unsafe 提供了一个getObject 方法 和putObject 方法 ,可以用来操作对象的字段。通过这两个 方法 ,我们可以获取和设置对象的私有字段。下面是一个示例代码: public class User { private int age; public User(int age) { this.age = age; // 使用 Unsafe 访问User对象的私有变量 public class Demo { public static void main(String[] args) throws Exception { Unsafe unsafe = Unsafe .get Unsafe (); User user = new User(18); long ageOffset = unsafe .objectFieldOffset(User.class.getDeclaredField("age")); int age = unsafe .getInt(user, ageOffset); System.out.println(age); unsafe .putInt(user, ageOffset, 20); System.out.println(user.getAge()); 在上面的代码中,我们定义了一个User ,它有一个私有字段age。然后在Demo 中,我们使用 Unsafe 的getObject和putObject 方法 来获取和设置User对象的age字段。使用 unsafe .objectFieldOffset 方法 获取age字段的偏移量,这个偏移量是用来定位age字段的地址。通过使用 unsafe .getInt和 unsafe .putInt 方法 ,我们可以对User对象的age字段进行读写操作。 访问私有 方法 Unsafe 还提供了一个allocateInstance 方法 ,可以在堆中为一个 分配内存,并且不会调用 的构造函数。通过这个 方法 ,我们可以创建一个对象,并且访问它的私有 方法 。下面是一个示例代码: public class User { private void hello() { System.out.println("hello"); // 使用 Unsafe 访问User对象的私有 方法 public class Demo { public static void main(String[] args) throws Exception { Unsafe unsafe = Unsafe .get Unsafe (); User user = (User) unsafe .allocateInstance(User.class); Method method = User.class.getDeclaredMethod("hello", null); method.setAccessible(true); method.invoke(user, null); 在上面的代码中,我们定义了一个User ,它有一个私有 方法 hello。然后在Demo 中,我们使用 Unsafe 的allocateInstance 方法 创建了一个User对象,并且使用反射机制访问了它的hello 方法 。使用method.setAccessible(true)可以将hello 方法 的访问权限修改为可访问。通过使用method.invoke 方法 ,我们可以调用User对象的hello 方法