LockSupport简介
LockSupport用来创建锁和其他同步类的基本线程阻塞原语。简而言之当调用LockSupport.park时表示当前线程将会等待直至获得许可当调用LockSupport.unpark时必须把等待获得许可的线程作为参数进行传递好让此线程继续运行。在AQS中大量使用AQS最终都是使用LockSupport来阻塞线程的。该类包含一组用于阻塞和唤醒线程的静态方法这些方法主要是围绕 park 和 unpark 展开话不多说直接来看一个简单的例子吧。javapublic 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源码分析类的属性javapublic 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函数下面给出两个函数的定义:javapublic 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函数有两个重载版本方法摘要如下javapublic static void park() public static void park(Object blocker)说明: 两个函数的区别在于park()函数没有没有blocker即没有设置线程的parkBlocker字段。park(Object)型函数如下。javapublic 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方法如下。javaprivate static void setBlocker(Thread t, Object arg) { // 设置线程t的parkBlocker字段的值为arg UNSAFE.putObject(t, parkBlockerOffset, arg); }说明: 此方法用于设置线程t的parkBlocker字段的值为arg。另外一个无参重载版本park()函数如下。javapublic static void park() { // 获取许可设置时间为无限长直到可以获取许可 UNSAFE.park(false, 0L); }说明: 调用了park函数后会禁用当前线程除非许可可用。在以下三种情况之一发生之前当前线程都将处于休眠状态即下列情况发生时当前线程会获取许可可以继续运行。其他某个线程将当前线程作为目标调用 unpark。其他某个线程中断当前线程。该调用不合逻辑地(即毫无理由地)返回。parkNanos函数此函数表示在许可可用前禁用当前线程并最多等待指定的等待时间。具体函数如下。javapublic 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); }