问题
- 为什么LockSupport 是核心基础类?
- 如果在wait()之前执行了notify()会怎样?
- 如果在park()之前执行了unpark()会怎样?
- 写出分别通过wait/notify和LockSupport的park/unpark实现同步?
- LockSupport.part() 与 Object.await()、Condition.await() 的区别是啥?
初始化
// Hotspot implementation via intrinsics API
private static final sun.misc.Unsafe UNSAFE;
private static final long parkBlockerOffset;
private static final long SEED;
private static final long PROBE;
private static final long SECONDARY;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> tk = Thread.class;
// hread 中 parkBlocker 属性的内存偏移地址
parkBlockerOffset = UNSAFE.objectFieldOffset
(tk.getDeclaredField("parkBlocker"));
// Thread 中 threadLocalRandomSeed 属性的内存偏移地址
SEED = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomSeed"));
// Thread 中 threadLocalRandomProbe 属性的内存偏移地址
PROBE = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomProbe"));
// Thread 中 threadLocalRandomSecondarySeed 属性的内存偏移地址
SECONDARY = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomSecondarySeed"));
} catch (Exception ex) { throw new Error(ex); }
}
// 私有构造函数,不能被实例化。
private LockSupport() {}
核心操作
当前线程被阻塞。
被唤醒的条件
- 其他线程调用 unpark
- 其他线程中断当前线程
- 超时
- 虚假唤醒
对于有条件的阻塞,在唤醒后必须立即重新检查之前的条件是否成立。建议使用 condition
// 如果线程在 park 上受阻塞,将解除其阻塞状态。
// 如果线程没有在 park 上受阻塞,线程下次调用 park 将不会阻塞。
// 如果给定线程尚未启动,则无法保证此操作有任何效果。
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
// 一直阻塞
public static void park() {
UNSAFE.park(false, 0L);
}
// 从现在开始阻塞 nanos 纳秒
public static void parkNanos(long nanos) {
if (nanos > 0)
UNSAFE.park(false, nanos);
}
// 从现在开始阻塞 nanos 纳秒
public static void parkUntil(long deadline) {
UNSAFE.park(true, deadline);
}
// 一直阻塞
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, 0L);
setBlocker(t, null);
}
// 从现在开始阻塞 nanos 纳秒
public static void parkNanos(Object blocker, long nanos) {
if (nanos > 0) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, nanos);
// 唤醒后设置线程的 parkBlocker 为 null
setBlocker(t, null);
}
}
// 阻塞到 deadline
public static void parkUntil(Object blocker, long deadline) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(true, deadline);
setBlocker(t, null);
}
public static Object getBlocker(Thread t) {
if (t == null)
throw new NullPointerException();
return UNSAFE.getObjectVolatile(t, parkBlockerOffset);
}
private static void setBlocker(Thread t, Object arg) {
// Even though volatile, hotspot doesn't need a write barrier here.
UNSAFE.putObject(t, parkBlockerOffset, arg);
}
注:parkBlocker 在 Thread 中没有实际意义,仅仅是用于向当前线程保存自定义的信息(比如调试、监控)。在调用 park 的时候,需要设置某些信息,其他线程可以通过 getBlocker 获取到设置的信息。在线程处于阻塞的时候,可以通过 getBlocker 拿到设置的信息。因此,理解 park/unpark 相关操作,可以忽略 blocker。
底层原理
park()/unpark()底层的原理是“二元信号量”,假设有个计数器 counter 初始为 0
park
- 如果 counter 为 1,将 counter 设置为 0,并返回;
- 如果 counter 为 0,等待,直到其他线程唤醒,将 counter 设置为 0,并返回。
unpark:
- 如果 counter 为 0,将 counter 设置为 1,并唤醒 park 线程,并返回;
- 如果 counter 为 1,立即返回。
情景 1:
- 线程 A 调用 unpark 的时候,counter 设置 1,并返回。
- 线程 A 继续多次调用 unpark,由于 counter 已经为 1,直接返回
- 线程 B 调用 park,将 counter 设置为 0,直接返回。
情景2:
- 线程 A 调用 park,此时,counter 为 0,阻塞
- 线程 B 调用 unpark,将 counter 设置为 1,并唤醒线程 A。
- 线程 A 从阻塞中恢复,将 counter 设置为 0,并返回;
仔细理解了 park 和 unpark 的底层是二元信号量的本质,那么,不关 park 和 unpark 怎么变化,都不会出错。
总结
LockSupport 本质就是二元信号量,理解了这个本质,LockSupport 就非常简单了。
附录
各种阻塞对比
比较 | LockSupport.park() | Object.wait() | condation.await() | Thread.sleep() |
---|---|---|---|---|
是否释放锁 | 不释放 | 释放锁 | 释放锁 | 不释放锁 |
指定等待时间 | 可选 | 可选 | 可选 | 必须 |
唤醒方式 | unpark或超时 | notify或超时 | notify 或超时 | 自动 |
底层原理 | UNSAFE.park | native | UNSAFE.park | native |
其他:
-
如果在wait()之前执行了notify()会怎样? 抛出IllegalMonitorStateException异常;
-
如果在park()之前执行了unpark()会怎样? 线程不会被阻塞,直接跳过park(),继续执行后续内容;
-
使用wait/notify实现同步时,必须先调用wait,后调用notify,如果先调用notify,再调用wait,将起不了作用。
使用 park/unpark 实现同步时,park 和 unpark 没有顺序要求。
- 线程 A,线程 B 启动,线程 A 先调用 park,线程 B 后调用 unpark,线程 A 在线程 B 调用 unpark 后解除阻塞。
- 线程 A,线程 B 启动,线程 A 调用 unpark,线程 B 先调用 park,线程 B 后调用 park 之后不会阻塞,因为 线程 A 已经调用了 unpark。