跳至主要內容

LockSupport 源码阅读

Java并发

概述

LockSupport 是 Java 并发包中提供的一个工具类,用于支持线程的阻塞和唤醒操作,通常与其他同步工具一起使用,例如 ReentrantLockCondition 等,以实现更复杂的线程协作。

public class LockSupport {
    private LockSupport() {}

    private static final Unsafe U = Unsafe.getUnsafe();
    private static final long PARKBLOCKER = U.objectFieldOffset(Thread.class, "parkBlocker");
    private static final long TID = U.objectFieldOffset(Thread.class, "tid");


    private static void setBlocker(Thread t, Object arg) {
        U.putReferenceOpaque(t, PARKBLOCKER, arg);
    }

    public static Object getBlocker(Thread t) {
        if (t == null)
            throw new NullPointerException();
        return U.getReferenceOpaque(t, PARKBLOCKER);
    }

    static final long getThreadId(Thread thread) {
        return U.getLong(thread, TID);
    }

    ......
}

LockSupport 提供了一种基于许可(permit)的机制,可以让线程在需要时阻塞等待许可,以及在其他线程发出信号时唤醒,它的无参构造器是私有的,只对外提供了一些公共静态方法。从源码中,可以看到 LockSupport 底层是基于 (Unsafe)[./Unsafe.md] 实现的,还需要借助 Unsafe 操作 Thread 对象的两个属性:parkBlocker阻塞对象和tid线程ID,其中线程的阻塞对象用于记录调用 park 方法而被阻塞的线程在哪个对象上进行了阻塞,主要是监测和调试目的。

public class Thread implements Runnable {
    
    ...

    private final long tid;

    volatile Object parkBlocker;

    ...
}

park

当在一个线程里调用 park 后,线程就会阻塞住,直到以下三种情况任意一个发生,获得许可后才可继续运行:

  • 其他某个线程将当前线程作为目标调用 unpark
  • 其他某个线程中断当前线程
  • 该调用不合逻辑地(即毫无理由地)返回
// 无限阻塞
public static void park() {
    U.park(false, 0L);
}

// 带阻塞对象的
public static void park(Object blocker) {
    Thread t = Thread.currentThread();
    // 设置阻塞对象
    setBlocker(t, blocker);
    // 线程阻塞在这里
    U.park(false, 0L);
    // 恢复后清空线程的阻塞对象
    setBlocker(t, null);
}

// 设定阻塞的最长等待时间(以及不带阻塞对象的重载)
public static void parkNanos(Object blocker, long nanos) {
    if (nanos > 0) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        U.park(false, nanos);
        setBlocker(t, null);
    }
}

// 设定阻塞的最长绝对时间(以及不带阻塞对象的重载)
public static void parkUntil(Object blocker, long deadline) {
    Thread t = Thread.currentThread();
    setBlocker(t, blocker);
    U.park(true, deadline);
    setBlocker(t, null);
}

unpark

unpark 可以解除某个线程的阻塞状态,或者保证该线程下次 park 时不会阻塞直接放行。如果线程还没有启动则无法保证能正确放行。

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

对比

LockSupport 可以作为线程间通信的基础工具,用于实现等待/通知模式,相比最简单的 Thread.sleep(),park 阻塞线程可以由外部唤醒。而相比于依赖对象监视器的 wait/notify 通知机制,LockSupport 功能更加强大,更加灵活。

特点park/unparkwait/notify
锁资源和 sleep() 一样,不会释放锁资源会释放
上下文任意上下文执行,不依赖于锁基于 Object、Monitor,需要在 synchronized 块中执行
中断不抛出异常,但可以通过线程中断状态检查如果被中断会抛出异常,需要手动处理
顺序许可机制,不需要考虑执行先后notify() 必须在 wait() 之后执行,否则线程可能一直阻塞

原理

实现上,每个线程都由一个自己的 Parker 对象(C语言层面),包括:

  • _counter: 计数器
  • _cond: 条件变量
  • _mutex: 互斥锁

当执行 park 时,当前线程先检查 _counter 的值:

  • 如果为 0,线程进入 _cond 条件变量的队列阻塞,并重设 _counter = 0
  • 如果为 1,线程无序阻塞,继续运行

当执行 unpark 时,当前线程先检查 _counter 的值:

  • 如果为 1,唤醒 _cond 条件变量中的线程,恢复其运行,重置 _counter = 0
  • 如果不为 1,设置 _counter = 1

总结一下,park/unpark 底层类似一个二元信号量,可以把它想象成只有一个许可证的Semaphore,只不过这个信号量在重复执行 unpark() 的时候也不会再增加许可证,最多只有一个许可证。

参考

  1. https://pdai.tech/md/java/thread/java-thread-x-lock-LockSupport.htmlopen in new window