类的属性
public class LockSupport {
// 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实例
UNSAFE = sun.misc.Unsafe.getUnsafe();
// 线程类类型
Class<?> tk = Thread.class;
// 获取Thread的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); }
}
}
核心函数分析
Unsafe 的 park 和 unpark 函数
- park函数,阻塞线程,并且该线程在下列情况发生之前都会被阻塞: ① 调用 unpark 函数,释放该线程的许可。② 该线程被中断。③ 设置的时间到了。并且,当 time 为绝对时间时,isAbsolute 为 true,否则,isAbsolute 为 false。当 time 为 0 时,表示无限等待,直到 unpark 发生。
- unpark 函数,释放线程的许可,即激活调用 park 后阻塞的线程。这个函数不是安全的,调用这个函数时要确保线程依旧存活。
park 函数
park 用于挂起当前线程,如果许可可用,会立即返回,并消费掉许可。
park 函数有两个重载版本,摘要如下:public static void park();
public static void park(Object blocker);
park(Object)
实现如下: ```java // java.util.concurrent.locks.LockSupport#park(java.lang.Object)
public static void park() { // 获取许可,设置时间为无限长,直到可以获取许可 UNSAFE.park(false, 0L); }
public static void park(Object blocker) { // #1 获取当前线程 Thread t = Thread.currentThread();
// #2 设置 Blocker
setBlocker(t, blocker);
// #3 获取许可
UNSAFE.park(false, 0L);
// #4 重新可运行后再将线程的 Blocker 恢复为 null
setBlocker(t, null);
}
private static void setBlocker(Thread t, Object arg) { // 设置线程t的parkBlocker字段的值为arg UNSAFE.putObject(t, parkBlockerOffset, arg); }
疑问:为什么要在 park(Object) 中两次调用 `setBlocker()` 函数呢? 主要是恢复线程。<br />每个线程都有一个 `volatile Object parkBlocker` ,专门服务于 `LockSupport` 工具类。<br />调用 `park()` 函数后,会将目标线程阻塞,当以下情况发生时,意味着许可可用:
1. 其它某个线程将当前线程作为目标调用 `unpark()` 函数。
1. 其它某个线程中断当前线程。
1. `absolute = false && time != 0`,过了给定的时间后线程被唤醒。
1. 虚假唤醒。
<a name="pdQrO"></a>
## unpark 函数
此函数表示如果给定的线程的许可尚不可用,则使其可用。如果线程阻塞于 park() 函数调用,则它将被解除阻塞状态。
<a name="AzTZM"></a>
# Demo
```java
// 获取许可,阻塞、可中断响应
LockSupport.park("ParkAndUnparkDemo");
import java.util.concurrent.locks.LockSupport;
class MyThread extends Thread {
private Object object;
public MyThread(Object object) {
this.object = object;
}
public void run() {
System.out.println("before unpark");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 获取blocker
System.out.println("Blocker info " + LockSupport.getBlocker((Thread) object));
// 释放许可
LockSupport.unpark((Thread) object);
// 休眠500ms,保证先执行park中的setBlocker(t, null);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 再次获取blocker
System.out.println("Blocker info " + LockSupport.getBlocker((Thread) object));
System.out.println("after unpark");
}
}
public class test {
public static void main(String[] args) {
MyThread myThread = new MyThread(Thread.currentThread());
myThread.start();
System.out.println("before park");
// 获取许可
LockSupport.park("ParkAndUnparkDemo");
System.out.println("after park");
}
}
深入理解
Thread.sleep() 和 Object.wait() 的区别
API | 是否释放锁 | 入参 |
---|---|---|
Thread.sleep() | 不会 | 必须传入时间 |
Object.wait | 会 | 可传可不传,不传表示一直阻塞 |
Thread.sleep() 和 Condition.await() 的区别
Object.wait() 和 Condition.await() 的原理是基本一致的,不同的是 Condition.await()
底层是调用 LockSupport.park()
来实现阻塞当前线程的。 实际上,它在阻塞当前线程之前还干了两件事,一是把当前线程添加到条件队列中,二是“完全”释放锁,也就是让 state 状态变量变为 0,然后才是调用 LockSupport.park() 阻塞当前线程。
Thread.sleep() 和 LockSupport.park() 的区别
LockSupport.park() 还有几个兄弟方法—— parkNanos()、parkUtil() 等,我们这里说的 park() 方法统称这一类方法。
- 阻塞当前线程且都不释放锁。
- Thread.sleep() 没法从外部唤醒,只能超时后被系统唤醒。LockSupport.park() 方法可以被另一个线程调用 LockSupport.unpark() 方法唤醒。
- Thread.sleep() 方法声明上抛出了 InterruptedException 中断异常,所以调用者需要捕获这个异常或者再抛出。LockSupport.park() 方法不需要捕获中断异常。
Thread.sleep() 本身就是一个 native 方法,LockSupport.park() 底层是调用的 Unsafe 的 native 方法。
Object.wait() 和 LockSupport.park() 的区别
二者都会阻塞当前线程的运行,他们有什么区别呢?
是否需要持有锁:Object.wait() 方法需要在 synchronized 块中执行,即得先持有锁,才能 wait,否则抛出异常。LockSupport.park() 可以在任意地方执行。
- 是否需要处理受检查异常:Object.wait() 方法声明抛出了中断异常,调用者需要捕获或者再抛出。LockSupport.park() 不需要捕获中断异常
- 是否继续执行后续代码:Object.wait() 不带超时的,需要另一个线程执行 notify() 来唤醒,但不一定继续执行后续代码。LockSupport.park() 不带超时的,需要另一个线程执行 unpark() 来唤醒,一定会继续执行后续代码。
- 如果在 wait() 之前执行了 notify() 会怎样? 抛出 IllegalMonitorStateException 异常。
- 如果在 park() 之前执行了 unpark() 会怎样? 线程不会被阻塞,直接跳过 park(),继续执行后续内容。park()/unpark() 底层的原理是“二元信号量”,你可以把它相像成只有一个许可证的 Semaphore,只不过这个信号量在重复执行 unpark() 的时候也不会再增加许可证,最多只有一个许可证。
LockSupport.park() 会释放锁资源吗?
不会,它只负责阻塞当前线程,释放锁资源实际上是在 Condition 的 await() 方法中实现的。中断
主线程因为调用park()
而被阻塞,而另一个线程因中断而退出,此时主线程被唤醒,继续执行下面代码。可见,interrupt 的作用和unpark
是一样的。
原理分析
LockSupport 类可以阻塞当前线程以及唤醒指定被阻塞的线程。主要是通过 park() 和 unpark(thread) 方法来实现阻塞和唤醒线程的操作的。
- 每个线程都有一个许可 (permit),permit 只有两个值 1 和 0,默认是 0。
- 当调用 unpark(thread) 方法,就会将 thread 线程的许可 permit 设置成 1(注意多次调用 unpark 方法,不会累加,permit 值还是 1)。
- 当调用 park() 方法,如果当前线程的 permit 是 1,那么将 permit 设置为 0,并立即返回。如果当前线程的 permit 是 0,那么当前线程就会阻塞,直到别的线程将当前线程的 permit 设置为 1。
- park 方法会将 permit 再次设置为 0,并返回。注意:因为 permit 默认是 0,所以一开始调用 park() 方法,线程必定会被阻塞。调用 unpark(thread) 方法后,会自动唤醒 thread 线程,即 park 方法立即返回。
park 系统方法会使线程进入 WAITING 状态或 TIMED_WAITING 等待状态。调用 unpark() 方法或中断都可以唤醒线程。
Blocker 的好处在于,在诊断问题的时候能够知道 park 的原因,推荐使用带 Object 的 park() 操作。
总结
- park/unpark能够精准的对线程进行唤醒和等待。
- linux上的实现是通过POSIX的线程API的等待、唤醒、互斥、条件来进行实现的
- park在执行过程中首选看是否有许可,有许可就立马返回,而每次unpark都会给许可设置成有,这意味着,可以先执行unpark,给予许可,再执行park立马自行,适用于producer快,而consumer还未完成的场景参考地址