基本概念
LockSupport 可以用来实现线程的阻塞/唤醒。
每个使用 LockSupport 的线程都会与一个许可关联:
- 若该许可有效,则线程可以继续执行。
- 若该许可无效,则线程进入阻塞,等待许可证生效后再继续执行。
关于许可,可类比为停车场的车位:
- 若车位是被占用(即不可用),想要停车(park)则必须等待车位空余出来。
- 通过 unpark 把车位空出来(即可用),则就可以直接停车。
关于 LockSupport ,它具有以下特点:
- 许可默认是失效的(不可用)
// 例子①:无法打印,因为许可失效,线程进入阻塞
LockSupport.park();
System.out.println(111);
// 例子②:先使许可生效,线程继续执行,打印输出
LockSupport.unpark(Thread.currentThread());
LockSupport.park();
System.out.println(111);
- 无法重入
// 第二次调用 park 时,线程进入阻塞。
LockSupport.unpark(Thread.currentThread());
LockSupport.park();
System.out.println(111);
LockSupport.park();
System.out.println(222);
原理分析
源码来自 JDK 1.7
1.内部构造
LockSupport 类的构造函数被私有化了,说明它无法被实例化。但是它的所有操作都是静态的,可直接调用。
private LockSupport() {}
再来看下它的成员变量:
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long parkBlockerOffset;
unsafe:该类用于可以直接操控内存,被 JDK 广泛用于自己的包中,如 java.nio 和java.util.concurrent。但是丝毫不建议在生产环境中使用这个类。因为这个 API 十分不安全、不轻便、而且不稳定。这个不安全的类提供了一个观察 HotSpot JVM 内部结构并且可以对其进行修改。有时它可以被用来在不适用C++调试的情况下学习虚拟机内部结构,有时也可以被拿来做性能监控和开发工具。
parkBlockerOffset:用于记录线程是被谁阻塞的。可以通过LockSupport 的 getBlocker 获取到阻塞的对象。主要用于监控和分析线程用的。
2.park 操作
该操作表示禁用当前线程,除非许可证可用。好比停车的动作,除非车位空闲出来,否则想要停车只能等待。该类支持三种不同的 park 操作:
// ①park
public static void park() {
unsafe.park(false, 0L);
}
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
unsafe.park(false, 0L);
setBlocker(t, null);
}
// 设置阻塞对象
private static void setBlocker(Thread t, Object arg) {
unsafe.putObject(t, parkBlockerOffset, arg);
}
// ②parkNanos
public static void parkNanos(long nanos) {...}
public static void parkNanos(Object blocker, long nanos) {...}
// ③parkUntil
public static void parkUntil(long deadline) {...}
public static void parkUntil(Object blocker, long deadline) {...}
存在以下 5 种情况,线程的状态会被改变:
- 其他线程将当前线程作用目标调用 unpark 方法。
// 创建线程并启动
Thread t1= new Thread(new Runnable() {
private int count =0;
public void run() {
// 默认许可不可用,线程进入阻塞。无法输出。
LockSupport.park();
System.out.println(111);
}
});
t1.start();
try {
// 主线程休眠一段时间,确保 t1 的动作先完成
Thread.currentThread().sleep(2000);
// 主线程将 t1 作为目标调用 unpark 方法,t1 被唤醒,打印输出
LockSupport.unpark(t1);
} catch (InterruptedException e) {
e.printStackTrace();
}
- 其他线程通过 interrupt 方法中断当前线程。
// 启动线程,代码同上
Thread t1= ...
t1.start();
try {
Thread.currentThread().sleep(2000);
// 主线程修改中断标志位, t1 线程中断
t1.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
调用不合逻辑地(即毫无理由地)返回
经过一段等待时间(针对 parkNanos)
到达截至时间(针对 parkUntil)
3.unPark 操作
该操作表示若线程的许可尚不可用,则使其可用。好比将车位的车挪出去,那么停车动作(park)就可以继续。
public static void unpark(Thread thread) {
if (thread != null){
unsafe.unpark(thread);
}
}