LockSupport源码分析

简介

Basic thread blocking primitives for creating locks and other synchronization classes. -- Java Doc

LockSupport是用来创建锁和其它同步类的基本线程阻塞原语。底层依赖Unsafe实现,我们可以在其它的并发同步工具类的实现中看到该类的使用。LockSupport提供了park()unpark()方法来分别实现阻塞线程和唤醒线程,每个使用LockSupport的线程都有一个permit,该值默认为0,取值0,1。

  • unpark():如果permit当前值为0,将其自增1。
  • park():如果当前permit为1,将其自减1并立即返回,如果为0,直接阻塞。

这两个方法不会有Thread.suspendThread.resume所可能引发的死锁问题,因为permit存在,调用 park的线程和另一个试图将其 unpark的线程之间的竞争将保持活性。

如果调用线程被中断,那么park将会返回。park方法可能在任何时间no reason地返回,因此通常在重新检查返回条件地循环里调用此方法。在某种意义上,parkbusy wait(忙则等待)的一种优化,减少了自旋对性能的消耗。当时必须与unpark配合使用才会更加高效。

park还提供了支持blocker参数的方法,blocker对象在线程受阻塞时被记录,用于允许监视和诊断工具确定线程被阻塞的原因。提供了getBlocker(Thread t)来访问blocker

下面是Java Docs中的示例用法:一个先进先出非重入锁类的基本框架:

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());
    }
  }}

源码解读

成员变量

  • UNSAFE:用于进行内存级别操作的工具类。
  • parkBlockerOffset:存储Thread.parkBlocker的内存偏移地址,记录线程被谁阻塞的。用于线程监控和分析工具用来定位原因的。可以通过getBlocker获取到阻塞的对象。
  • SEED:存储Thread.threadLocalRandomSeed的内存偏移地址
  • PROBE:存储Thread.threadLocalRandomProbe的内存偏移地址
  • SECONDARY:存储Thread.threadLocalRandomSecondarySeed的内存偏移地址
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;

构造方法

不允许实例化,只能通过调用静态方法来完成操作。

private LockSupport() {} // Cannot be instantiated.

静态代码块

通过反射机制获取Thread类的parkBlocker字段,然后通过UNSAFE.objectFieldOffset获取到parkBlocker在内存的偏移量。

Q:为什么不通过get/set方式获取某个字段?

A:parkBlocker在线程处于阻塞状态下才会被赋值,此时直接调用线程内的方法,线程不会作出回应的。

static {
        try {
            //获取unsafe实例
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            Class<?> tk = Thread.class;
            parkBlockerOffset = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("parkBlocker"));
            SEED = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomSeed"));
            PROBE = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomProbe"));
            SECONDARY = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomSecondarySeed"));
        } catch (Exception ex) { throw new Error(ex); }
    }

setBlocker

对给定的线程tparkBlocker赋值。

private static void setBlocker(Thread t, Object arg) {
        // Even though volatile, hotspot doesn't need a write barrier here.
        UNSAFE.putObject(t, parkBlockerOffset, arg);
    }

getBlocker

返回线程tparkBlocker对象。

public static Object getBlocker(Thread t) {
        if (t == null)
            throw new NullPointerException();
        return UNSAFE.getObjectVolatile(t, parkBlockerOffset);
    }

park

park方法阻塞线程,发生以下情况时,当前线程会继续执行:

  • 其它线程调用unpark方法唤醒该线程
  • 其它线程中断当前线程
  • no reason地返回

该方法有两个重载版本。

Q:为什么调用两次setBlocker方法?

A:调用park方法时,当前线程首先设置好parkBlocker字段,然后调用UNSAFE.park方法,此时,当前线程阻塞,第二个setBlocker无法执行,过了一段时间,该线程的unpark方法被调用,该线程拿到permit后执行,将该线程的blocker字段置空。

public static void park(Object blocker) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        UNSAFE.park(false, 0L);
        setBlocker(t, null);
    }

public static void park() {
        UNSAFE.park(false, 0L);
    }

parkNanos

阻塞当前线程,最长不超过nanos纳秒

public static void parkNanos(long nanos) {
        if (nanos > 0)
            UNSAFE.park(false, nanos);
    }

public static void parkNanos(Object blocker, long nanos) {
        if (nanos > 0) {
            Thread t = Thread.currentThread();
            setBlocker(t, blocker);
            UNSAFE.park(false, nanos);
            setBlocker(t, null);
        }
    }

parkUntil

该方法表示在限定的时间内将阻塞。

public static void parkUntil(long deadline) {
        UNSAFE.park(true, deadline);
    }

unpark

如果给定线程的permit不可用,则将其置为可用,如果该线程阻塞,则将它解除阻塞状态。否则,保证下一次调用park不会受阻塞。如果给定线程尚未启动,则无法保证该操作有任何效果。

public static void unpark(Thread thread) {
    if (thread != null)
        UNSAFE.unpark(thread);
}

参考

Java SE Doc


LockSupport源码分析
https://l1n.wang/2020/Java并发/LockSupport源码分析/
作者
Lin Wang
发布于
2020年11月8日
许可协议