前言

LockSupport 是 JUC 中罕用的一个工具类,次要作用是挂起和唤醒线程。在浏览 JUC 源码中常常看到,所以很有必要理解一下。

公众号:liuzhihangs ,记录工作学习中的技术、开发及源码笔记;时不时分享一些生存中的见闻感悟。欢送大佬来领导!

介绍

根本线程阻塞原语创立锁和其余同步类。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 。

下面的意思总结下来集体了解是:

  1. 许可(permit)的下限是1,也就是说只有 0 或 1 。
  2. park: 没有许可的时候,permit 为 0 ,调用 park 会阻塞;有许可的时候,permit 为 1 , 调用 park 会扣除一个许可,而后返回。
  3. unpark:没有许可的时候,permit 为 0 ,调用 unpark 会减少一个许可,因为许可下限是 1 , 所以调用屡次也只会为 1 个。
  4. 线程初始的时候是没有许可的。
  5. park 的以后线程如果被中断,会立刻返回,并不会抛出中断异样。
  6. park 办法的调用个别要放在一个循环判断体外面。

大略如图所示:

上面是源码正文中的案例:

/** * FIFO 独占锁 */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);     // Block while not first in queue or cannot acquire lock     // 不在队列头,或者锁被占用,则阻塞, 就是只有队列头的能够取得锁     while (waiters.peek() != current || !locked.compareAndSet(false, true)) {       LockSupport.park(this);       if (Thread.interrupted()) // ignore interrupts while waiting         wasInterrupted = true;     }     waiters.remove();     if (wasInterrupted)          // reassert interrupt status on exit       current.interrupt();   }   public void unlock() {     locked.set(false);     LockSupport.unpark(waiters.peek());   } }

验证

线程初始有没有许可?

public class LockSupportTest {    public static void main(String[] args) {        System.out.println("开始执行……");        LockSupport.park();        System.out.println("LockSupport park 之后……");    }}
  1. 执行后会发现,代码在 park 处阻塞。阐明,线程初始是没有许可的。

增加许可并耗费许可

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 之后……");    }}
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) 完结");    }}

通过下面示例能够看出:

  1. 执行 unpark 能够进行给予指定线程一个证书。
  2. 线程以后被 park 阻塞,此时给予证书之后, park 会耗费证书,而后继续执行。

许可下限为 1

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 之后……");    }}
  1. 线程阻塞,能够看出 permit 只能有一个

中断能够使 park 继续执行并不会抛出异样

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)");            // LockSupport.unpark(thread);            thread.interrupt();            Thread.sleep(5000);            System.out.println("执行 unpark(thread) 完结");        } catch (InterruptedException e) {            e.printStackTrace();        }    }}

输入后果:

/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) 完结
  1. 能够看出线程中断,park 会继续执行,并且没有抛出异样。
  2. thread.interrupt(); 调用之后, 设置线程中断标示,unpark 没有革除中断标示,第二个 park 也会继续执行。

应用诊断工具

liuzhihang % > jps76690 LockSupportTest77130 Jpsliuzhihang % > jstack 77265..."main" #1 prio=5 os_prio=31 tid=0x00007f7f3e80a000 nid=0xe03 waiting on condition [0x000070000dfcd000]   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)
  1. 两头省略局部,然而能够看出线程进入 WAITING 状态

源码剖析

public class LockSupport {    private static final sun.misc.Unsafe UNSAFE;    /**     * 为线程 thread 设置一个许可     * 无许可,则增加一个许可,有许可,则不增加     * 如果线程因为 park 被阻塞, 增加许可之后,会解除阻塞状态     */    public static void unpark(Thread thread) {        if (thread != null)            UNSAFE.unpark(thread);    }    /**     * 有许可,则应用该许可     * 没有许可,阻塞线程,直到取得许可     * 传递 blocker 是为了方便使用诊断工具     */    public static void park(Object blocker) {        Thread t = Thread.currentThread();        setBlocker(t, blocker);        UNSAFE.park(false, 0L);        setBlocker(t, null);    }    /**     * 设置线程的 blocker 属性     */    private static void setBlocker(Thread t, Object arg) {        // Even though volatile, hotspot doesn't need a write barrier here.        UNSAFE.putObject(t, parkBlockerOffset, arg);    }}

LockSupport 的 park unpark 办法,理论调用的是底层 Unsafe 类的 native 办法。

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/jd...

这块在以 os_linux 代码为例
http://hg.openjdk.java.net/jd...



  1. 在底层保护了一个 _counter 通过更新 _counter 的值来标示是否有证实。
  2. 在 park 时,判断 _counter 为 0,则阻塞期待,为 1 则取得更新为 0 并返回。
  3. 在 unpark 时,判断 _counter 为 0,则给予凭证,并唤醒线程,为 1 则间接返回。

总结

总结也是和料想的是雷同的。

  1. 许可(permit)的下限是1,也就是说只有 0 或 1 。
  2. park: 没有许可的时候,permit 为 0 ,调用 park 会阻塞;有许可的时候,permit 为 1 , 调用 park 会扣除一个许可,而后返回。
  3. unpark:没有许可的时候,permit 为 0 ,调用 unpark 会减少一个许可,因为许可下限是 1 , 所以调用屡次也只会为 1 个。
  4. 线程初始的时候是没有许可的。
  5. park 的以后线程如果被中断,会立刻返回,并不会抛出中断异样。

扩大

  • park/unpark 和 wait/notify 区别
  1. park 阻塞以后线程,unpark 唤醒指定线程。
  2. wait() 须要联合锁应用,并开释锁资源,如果没有设置超时工夫,则须要 notify() 唤醒。而 notify() 是随机唤醒线程。