概述
LockSupport 是 JUC 包中的一个工具类,是用来创建锁和其他同步类的基本线程阻塞原语。
因为 LockSupport 是锁中的基础。比如 AQS 中就需要用到,所以先了解一哈。
先把源码放上:JDK8 LockSupport 源码带注释
好了,现在开始。。。
LockSupport 类的核心方法其实就两个: park()
和 unpark()
LockSupport.park()
方法用来阻塞当前调用线程LockSupport.unpark()
方法用于唤醒指定线程。
LockSupport 没有开放构造方法,所以只能通过 静态方法 来使用。
private LockSupport() {} // 私有构造函数,无法实例化。
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
字段介绍可以翻一翻:CAS、Unsafe、Atomic原子类,一波带走
这些字段和属性的作用可以总结为:为 LockSupport 提供 CAS 机制。
核心方法
因为 LockSupport
底层使用 UnSafe
类实现,而核心函数就是下面两个:
public native void park(boolean isAbsolute, long time);
public native void unpark(Thread thread);
- park函数:阻塞线程,并且该线程在下列情况发生之前都会被阻塞:
- ① 调用unpark函数,释放该线程的许可。
- ② 该线程被中断。
- ③ 设置的时间到了。并且,当time为绝对时间时,isAbsolute 为 true,否则,isAbsolute 为 false。当time为0时,表示无限等待,直到unpark发生。
- unpark函数:释放线程的许可,即激活调用park后阻塞的线程。这个函数不是安全的,调用这个函数时要确保线程依旧存活。
park 相关方法
首先是基础 park 方法,提供了永久等待、定时等待、确定截至时间,三种方式。
// 除非许可,否则出于线程调度目的禁用当前线程。
public static void park() {
UNSAFE.park(false, 0L);
}
// 为线程调度目的禁用当前线程,直至指定的等待时间,除非许可可用。
public static void parkNanos(long nanos) {
if (nanos > 0)
UNSAFE.park(false, nanos);
}
// 出于线程调度目的禁用当前线程,直到指定的截止日期,除非许可可用。
public static void parkUntil(long deadline) {
UNSAFE.park(true, deadline);
}
这些的使用方法就比较明显,然后有点区别的是带 blocker 的方法:
// 除非许可,否则出于线程调度目的禁用当前线程。添加同步对象
public static void park(Object blocker) {
// 获取当前线程
Thread t = Thread.currentThread();
// 设置Blocker
setBlocker(t, blocker);
// 获取许可
UNSAFE.park(false, 0L);
// 重新可运行后再此设置Blocker
setBlocker(t, null);
}
调用park函数时,首先获取当前线程,然后设置当前线程的parkBlocker字段,即调用setBlocker函数,之后调用Unsafe类的park函数,之后再调用setBlocker函数。
那么问题来了,为什么要在此park函数中要调用两次setBlocker函数呢?
原因其实很简单,调用 park 函数时,当前线程首先设置好 parkBlocker 字段,然后再调用 Unsafe 的 park 函数,此后,当前线程就已经阻塞了,等待该线程的unpark函数被调用,所以后面的一个setBlocker函数无法运行,unpark函数被调用,该线程获得许可后,就可以继续运行了,也就运行第二个setBlocker,把该线程的parkBlocker字段设置为null,这样就完成了整个park函数的逻辑。
如果没有第二个 setBlocker,那么之后没有调用 park(Object blocker),而直接调用 getBlocker函数,得到的还是前一个 park(Object blocker) 设置的 blocker,显然是不符合逻辑的。
总之,必须要保证在 park(Object blocker) 整个函数执行完后,该线程的 parkBlocker 字段又恢复为 null。所以,park(Object)型函数里必须要调用setBlocker函数两次。setBlocker方法如下。
unpark 方法
此函数表示如果给定线程的许可尚不可用,则使其可用。
如果线程在 park 上受阻塞,则它将解除其阻塞状态。否则,保证下一次调用 park 不会受阻塞。
如果给定线程尚未启动,则无法保证此操作有任何效果。具体函数如下:
/**
* 使给定线程的许可可用(如果它尚不可用)。
* 如果线程在 {@code park} 上被阻塞,那么它将解除阻塞。
* 否则,它对 {@code park} 的下一次调用保证不会阻塞。如果给定的线程尚未启动,则无法保证此操作有任何效果。
* @param thread 线程非null情况下,取消线程停放
*/
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
LockSupport示例说明
使用wait/notify实现线程同步
class MyThread extends Thread {
public void run() {
synchronized (this) {
System.out.println("before notify");
notify();
System.out.println("after notify");
}
}
}
public class WaitAndNotifyDemo {
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
synchronized (myThread) {
try {
myThread.start();
// 主线程睡眠3s
Thread.sleep(3000);
System.out.println("before wait");
// 阻塞主线程
myThread.wait();
System.out.println("after wait");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行结果
before wait
before notify
after notify
after wait
使用wait/notify实现同步时,必须先调用wait,后调用notify,如果先调用notify,再调用wait,将起不了作用。具体代码如下
class MyThread extends Thread {
public void run() {
synchronized (this) {
System.out.println("before notify");
notify();
System.out.println("after notify");
}
}
}
public class WaitAndNotifyDemo {
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
myThread.start();
// 主线程睡眠3s
Thread.sleep(3000);
synchronized (myThread) {
try {
System.out.println("before wait");
// 阻塞主线程
myThread.wait();
System.out.println("after wait");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行结果:
before notify
after notify
before wait
说明: 由于先调用了notify,再调用的wait,此时主线程还是会一直阻塞。
使用park/unpark实现线程同步
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");
}
}
运行结果:
before park
before unpark
Blocker info ParkAndUnparkDemo
after park
Blocker info null
after unpark
说明: 本程序先执行park,然后在执行unpark,进行同步,并且在unpark的前后都调用了getBlocker,可以看到两次的结果不一样,并且第二次调用的结果为null,这是因为在调用unpark之后,执行了Lock.park(Object blocker)函数中的setBlocker(t, null)函数,所以第二次调用getBlocker时为null。
上例是先调用park,然后调用unpark,现在修改程序,先调用unpark,然后调用park,看能不能正确同步。具体代码如下:
class MyThread extends Thread {
private Object object;
public MyThread(Object object) {
this.object = object;
}
public void run() {
System.out.println("before unpark");
// 释放许可
LockSupport.unpark((Thread) object);
System.out.println("after unpark");
}
}
public class ParkAndUnparkDemo {
public static void main(String[] args) {
MyThread myThread = new MyThread(Thread.currentThread());
myThread.start();
try {
// 主线程睡眠3s
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("before park");
// 获取许可
LockSupport.park("ParkAndUnparkDemo");
System.out.println("after park");
}
}
运行结果:
before unpark
after unpark
before park
after park
说明: 可以看到,在先调用unpark,再调用park时,仍能够正确实现同步,不会造成由wait/notify调用顺序不当所引起的阻塞。因此 park/unpark 相比 wait/notify 更加的灵活。
中断响应
class MyThread extends Thread {
private Object object;
public MyThread(Object object) {
this.object = object;
}
public void run() {
System.out.println("before interrupt");
try {
// 休眠3s
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread thread = (Thread) object;
// 中断线程
thread.interrupt();
System.out.println("after interrupt");
}
}
public class InterruptDemo {
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");
}
}
运行结果:
before park
before interrupt
after interrupt
after park
说明: 可以看到,在主线程调用park阻塞后,在myThread线程中发出了中断信号,此时主线程会继续运行,也就是说明此时interrupt起到的作用与unpark一样。