LockSupport简介
LockSupport用来创建锁和其他同步类的基本线程阻塞原语。简而言之,当调用LockSupport.park时,表示当前线程将会等待,直至获得许可,当调用LockSupport.unpark时,必须把等待获得许可的线程作为参数进行传递,好让此线程继续运行。在AQS中大量使用,AQS最终都是使用LockSupport来阻塞线程的。
该类包含一组用于阻塞和唤醒线程的静态方法,这些方法主要是围绕 park 和 unpark 展开,话不多说,直接来看一个简单的例子吧。
java
public class LockSupportDemo1 { public static void main(String[] args) { Thread mainThread = Thread.currentThread(); // 创建一个线程从1数到1000 Thread counterThread = new Thread(() -> { for (int i = 1; i <= 1000; i++) { System.out.println(i); if (i == 500) { // 当数到500时,唤醒主线程 LockSupport.unpark(mainThread); } } }); counterThread.start(); // 主线程调用park LockSupport.park(); System.out.println("Main thread was unparked."); } }上面的代码中,当 counterThread 数到 500 时,它会唤醒 mainThread。而 mainThread 在调用 park 方法时会被阻塞,直到被 unpark。
LockSupport 中的方法不多,这里将这些方法做一个总结:
阻塞线程
void park():阻塞当前线程,如果调用 unpark 方法或线程被中断,则该线程将变得可运行。请注意,park 不会抛出 InterruptedException,因此线程必须单独检查其中断状态。void park(Object blocker):功能同方法 1,入参增加一个 Object 对象,用来记录导致线程阻塞的对象,方便问题排查。void parkNanos(long nanos):阻塞当前线程一定的纳秒时间,或直到被 unpark 调用,或线程被中断。void parkNanos(Object blocker, long nanos):功能同方法 3,入参增加一个 Object 对象,用来记录导致线程阻塞的对象,方便问题排查。void parkUntil(long deadline):阻塞当前线程直到某个指定的截止时间(以毫秒为单位),或直到被 unpark 调用,或线程被中断。void parkUntil(Object blocker, long deadline):功能同方法 5,入参增加一个 Object 对象,用来记录导致线程阻塞的对象,方便问题排查。
唤醒线程
void unpark(Thread thread):唤醒一个由 park 方法阻塞的线程。如果该线程未被阻塞,那么下一次调用 park 时将立即返回。这允许“先发制人”式的唤醒机制。
实际上,LockSupport 阻塞和唤醒线程的功能依赖于sun.misc.Unsafe,这是一个很底层的类,比如 LockSupport 的 park 方法是通过unsafe.park()方法实现的。
LockSupport源码分析
类的属性
java
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字段表示sun.misc.Unsafe类,一般程序中不允许直接调用,而long型的表示实例对象相应字段在内存中的偏移地址,可以通过该偏移地址获取或者设置该字段的值。
类的构造函数
java
// 私有构造函数,无法被实例化 private LockSupport() {}说明: LockSupport只有一个私有构造函数,无法被实例化。
核心函数分析
在分析LockSupport函数之前,先引入sun.misc.Unsafe类中的park和unpark函数,因为LockSupport的核心函数都是基于Unsafe类中定义的park和unpark函数,下面给出两个函数的定义:
java
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函数有两个重载版本,方法摘要如下
java
public static void park(); public static void park(Object blocker);
说明: 两个函数的区别在于park()函数没有没有blocker,即没有设置线程的parkBlocker字段。park(Object)型函数如下。
java
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方法如下。
java
private static void setBlocker(Thread t, Object arg) { // 设置线程t的parkBlocker字段的值为arg UNSAFE.putObject(t, parkBlockerOffset, arg); }说明: 此方法用于设置线程t的parkBlocker字段的值为arg。
另外一个无参重载版本,park()函数如下。
java
public static void park() { // 获取许可,设置时间为无限长,直到可以获取许可 UNSAFE.park(false, 0L); }说明: 调用了park函数后,会禁用当前线程,除非许可可用。在以下三种情况之一发生之前,当前线程都将处于休眠状态,即下列情况发生时,当前线程会获取许可,可以继续运行。
其他某个线程将当前线程作为目标调用 unpark。
其他某个线程中断当前线程。
该调用不合逻辑地(即毫无理由地)返回。
parkNanos函数
此函数表示在许可可用前禁用当前线程,并最多等待指定的等待时间。具体函数如下。
java
public static void parkNanos(Object blocker, long nanos) { if (nanos > 0) { // 时间大于0 // 获取当前线程 Thread t = Thread.currentThread(); // 设置Blocker setBlocker(t, blocker); // 获取许可,并设置了时间 UNSAFE.park(false, nanos); // 设置许可 setBlocker(t, null); }