LockSupport 是 JUC 中常用的一个工具类,主要作用是挂起和唤醒线程。在阅读 JUC 源码中经常看到,所以很有必要了解一下。
介绍
基本线程阻塞原语创建锁和其他同步类。Basic thread blocking primitives for creating locks and other synchronization classes.
LockSupport 类每个使用它的线程关联一个许可(在意义上的Semaphore类)。 如果许可可用,调用 park 将立即返回,并在此过程中消费它; 否则可能阻塞。如果许可不是可用,可以调用 unpark 使得许可可用。(但与Semaphore不同,许可不能累积。最多有一个。)
方法 park 和 unpark 提供了阻塞的有效手段和解锁线程不会遇到死锁问题,而 Thread.suspend 和 Thread.resume 是不能用于这种目的:因为许可的存在,一个线程调用 park 和另一个线程试图 unpark 它之间的竞争将保持活性。 此外,如果调用者线程被中断,park 将返回,并且支持设置超时。 该 park 方法也可能返回在其他任何时间,“毫无理由”,因此通常必须在一个循环中调用的返回后重新检查条件。 在这个意义上park作为“忙碌等待”不会浪费太多的时间自旋的优化,但必须以配对 unpark 使用。
这三种形式的 park 还支持 blocker 对象参数。而线程被阻塞时是允许使用监测和诊断工具,以确定线程被阻塞的原因。(诊断工具可以使用getBlocker(Thread) 方法 。)同时推荐使用带有 blocker 参数的 park方法,通常做法是 blocker 被设置为 this 。
上面的意思总结下来个人理解是:
许可(permit)的上限是1,也就是说只有 0 或 1 。
park: 没有许可的时候,permit 为 0 ,调用 park 会阻塞;有许可的时候,permit 为 1 , 调用 park 会扣除一个许可,然后返回。
unpark:没有许可的时候,permit 为 0 ,调用 unpark 会增加一个许可,因为许可上限是 1 , 所以调用多次也只会为 1 个。
线程初始的时候是没有许可的。
park 的当前线程如果被中断,会立即返回,并不会抛出中断异常。
park 方法的调用一般要放在一个循环判断体里面。
大概如图所示:
下面是源码注释中的案例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class FIFOMutex { private final AtomicBoolean locked = new AtomicBoolean (false ); private final Queue<Thread> waiters = new ConcurrentLinkedQueue <Thread>(); public void lock () { boolean wasInterrupted = false ; Thread current = Thread.currentThread(); waiters.add(current); while (waiters.peek() != current || !locked.compareAndSet(false , true )) { LockSupport.park(this ); if (Thread.interrupted()) wasInterrupted = true ; } waiters.remove(); if (wasInterrupted) current.interrupt(); } public void unlock () { locked.set(false ); LockSupport.unpark(waiters.peek()); } }
验证 线程初始有没有许可? 1 2 3 4 5 6 7 8 9 10 11 12 public class LockSupportTest { public static void main (String[] args) { System.out.println("开始执行……" ); LockSupport.park(); System.out.println("LockSupport park 之后……" ); } }
执行后会发现,代码在 park 处阻塞。说明,线程初始是没有许可的。
添加许可并消耗许可 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class LockSupportTest { public static void main (String[] args) { System.out.println("开始执行……" ); LockSupport.unpark(Thread.currentThread()); System.out.println("执行 - park" ); LockSupport.park(); System.out.println("LockSupport park 之后……" ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class LockSupportTest { public static void main (String[] args) throws InterruptedException { Thread thread = new Thread (new Runnable () { @Override public void run () { System.out.println("线程 " + Thread.currentThread().getName() + "开始执行 park" ); LockSupport.park(this ); System.out.println("线程 " + Thread.currentThread().getName() + "执行 park 结束" ); } }); thread.start(); Thread.sleep(5000 ); System.out.println("开始执行 unpark(thread)" ); LockSupport.unpark(thread); Thread.sleep(5000 ); System.out.println("执行 unpark(thread) 结束" ); } }
通过上面示例可以看出:
执行 unpark 可以进行给予指定线程一个证书。
线程当前被 park 阻塞,此时给予证书之后, park 会消耗证书,然后继续执行。
许可上限为 1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class LockSupportTest { public static void main (String[] args) { System.out.println("unpark 1次" ); LockSupport.unpark(Thread.currentThread()); System.out.println("unpark 2次" ); LockSupport.unpark(Thread.currentThread()); System.out.println("执行 - park 1 次" ); LockSupport.park(); System.out.println("执行 - park 2 次" ); LockSupport.park(); System.out.println("LockSupport park 之后……" ); } }
线程阻塞,可以看出 permit 只能有一个
中断可以使 park 继续执行并不会抛出异常 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public class LockSupportTest { public static void main (String[] args) { Thread thread = new Thread (new Runnable () { @Override public void run () { System.out.println("线程 " + Thread.currentThread().getName() + "开始执行 park" ); LockSupport.park(this ); System.out.println("线程 " + Thread.currentThread().getName() + "执行 park 结束" ); System.out.println("线程 " + Thread.currentThread().getName() + "开始执行 park 第二次" ); LockSupport.park(this ); System.out.println("线程 " + Thread.currentThread().getName() + "执行 park 第二次 结束" ); } }); try { thread.start(); Thread.sleep(5000 ); System.out.println("开始执行 unpark(thread)" ); thread.interrupt(); Thread.sleep(5000 ); System.out.println("执行 unpark(thread) 结束" ); } catch (InterruptedException e) { e.printStackTrace(); } } }
输出结果:
1 2 3 4 5 6 7 /Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/bin/java ... 线程 Thread-0开始执行 park 开始执行 unpark(thread) 线程 Thread-0执行 park 结束 线程 Thread-0开始执行 park 第二次 线程 Thread-0执行 park 第二次 结束 执行 unpark(thread) 结束
可以看出线程中断,park 会继续执行,并且没有抛出异常。
thread.interrupt(); 调用之后, 设置线程中断标示,unpark 没有清除中断标示,第二个 park 也会继续执行。
使用诊断工具 1 2 3 4 5 6 7 8 9 10 liuzhihang % > jps 76690 LockSupportTest 77130 Jps liuzhihang % > jstack 77265 ... "main" java.lang.Thread.State: WAITING (parking) at sun.misc.Unsafe.park(Native Method) at java.util.concurrent.locks.LockSupport.park(LockSupport.java:304) at com.liuzhihang.source.LockSupportTest.main(LockSupportTest.java:14)
中间省略部分,但是可以看出线程进入 WAITING
状态
源码分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public class LockSupport { private static final sun.misc.Unsafe UNSAFE; public static void unpark (Thread thread) { if (thread != null ) UNSAFE.unpark(thread); } 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); } }
LockSupport 的 park unpark 方法,实际调用的是底层 Unsafe 类的 native 方法。
1 2 3 4 5 6 7 public final class Unsafe { public native void unpark (Object var1) ; public native void park (boolean var1, long var2) ; }
既然调用了 Unsafe 到此处肯定不能善罢甘休。
hotspot 源码 这块是下载的官方包中的源码,阅读并查阅资料了解的大概逻辑,不清楚之处,希望指导出来。
也可以直接跳过直接看结论。
查看jdk源码http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/5a83b7215107/src/share/vm/runtime/park.hpp
这块在以 os_linux 代码为例http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/5a83b7215107/src/os/linux/vm/os_linux.cpp
在底层维护了一个 _counter
通过更新 _counter
的值来标示是否有证明。
在 park 时,判断 _counter
为 0,则阻塞等待,为 1 则获得更新为 0 并返回。
在 unpark 时,判断 _counter
为 0,则给予凭证,并唤醒线程,为 1 则直接返回。
总结 总结也是和预想的是相同的。
许可(permit)的上限是1,也就是说只有 0 或 1 。
park: 没有许可的时候,permit 为 0 ,调用 park 会阻塞;有许可的时候,permit 为 1 , 调用 park 会扣除一个许可,然后返回。
unpark:没有许可的时候,permit 为 0 ,调用 unpark 会增加一个许可,因为许可上限是 1 , 所以调用多次也只会为 1 个。
线程初始的时候是没有许可的。
park 的当前线程如果被中断,会立即返回,并不会抛出中断异常。
扩展
park/unpark 和 wait/notify 区别
park 阻塞当前线程,unpark 唤醒指定线程。
wait() 需要结合锁使用,并释放锁资源,如果没有设置超时时间,则需要 notify() 唤醒。而 notify() 是随机唤醒线程。