说明:本篇博客整理自文末的多篇参考博客(每篇博客各有侧重)。本文结合源码对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¬ify相比
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
方法
。