5 分钟掌握 JDK 源码之 LockSupport

问题

  • 为什么LockSupport 是核心基础类?
  • 如果在wait()之前执行了notify()会怎样?
  • 如果在park()之前执行了unpark()会怎样?
  • 写出分别通过wait/notify和LockSupport的park/unpark实现同步?
  • LockSupport.part() 与 Object.await()、Condition.await() 的区别是啥?

初始化

    // 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 = sun.misc.Unsafe.getUnsafe();
            Class<?> tk = Thread.class;
            // hread 中 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); }
    }

    // 私有构造函数,不能被实例化。
		private LockSupport() {}

核心操作

当前线程被阻塞。

被唤醒的条件

  1. 其他线程调用 unpark
  2. 其他线程中断当前线程
  3. 超时
  4. 虚假唤醒

对于有条件的阻塞,在唤醒后必须立即重新检查之前的条件是否成立。建议使用 condition

    // 如果线程在 park 上受阻塞,将解除其阻塞状态。
    // 如果线程没有在 park 上受阻塞,线程下次调用 park 将不会阻塞。
    // 如果给定线程尚未启动,则无法保证此操作有任何效果。
    public static void unpark(Thread thread) {
        if (thread != null)
            UNSAFE.unpark(thread);
    }

    // 一直阻塞
    public static void park() {
        UNSAFE.park(false, 0L);
    }

		// 从现在开始阻塞 nanos 纳秒
    public static void parkNanos(long nanos) {
        if (nanos > 0)
            UNSAFE.park(false, nanos);
    }

    // 从现在开始阻塞 nanos 纳秒
    public static void parkUntil(long deadline) {
        UNSAFE.park(true, deadline);
    }

    // 一直阻塞
    public static void park(Object blocker) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        UNSAFE.park(false, 0L);
        setBlocker(t, null);
    }
    
    // 从现在开始阻塞 nanos 纳秒
    public static void parkNanos(Object blocker, long nanos) {
        if (nanos > 0) {
            Thread t = Thread.currentThread();
            setBlocker(t, blocker);
            UNSAFE.park(false, nanos);
            // 唤醒后设置线程的 parkBlocker 为 null
            setBlocker(t, null);
        }
    }

    // 阻塞到 deadline
    public static void parkUntil(Object blocker, long deadline) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        UNSAFE.park(true, deadline);
        setBlocker(t, null);
    }


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

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

注:parkBlocker 在 Thread 中没有实际意义,仅仅是用于向当前线程保存自定义的信息(比如调试、监控)。在调用 park 的时候,需要设置某些信息,其他线程可以通过 getBlocker 获取到设置的信息。在线程处于阻塞的时候,可以通过 getBlocker 拿到设置的信息。因此,理解 park/unpark 相关操作,可以忽略 blocker。

底层原理

park()/unpark()底层的原理是“二元信号量”,假设有个计数器 counter 初始为 0

park

  1. 如果 counter 为 1,将 counter 设置为 0,并返回;
  2. 如果 counter 为 0,等待,直到其他线程唤醒,将 counter 设置为 0,并返回。

unpark:

  1. 如果 counter 为 0,将 counter 设置为 1,并唤醒 park 线程,并返回;
  2. 如果 counter 为 1,立即返回。

情景 1:

  1. 线程 A 调用 unpark 的时候,counter 设置 1,并返回。
  2. 线程 A 继续多次调用 unpark,由于 counter 已经为 1,直接返回
  3. 线程 B 调用 park,将 counter 设置为 0,直接返回。

情景2:

  1. 线程 A 调用 park,此时,counter 为 0,阻塞
  2. 线程 B 调用 unpark,将 counter 设置为 1,并唤醒线程 A。
  3. 线程 A 从阻塞中恢复,将 counter 设置为 0,并返回;

仔细理解了 park 和 unpark 的底层是二元信号量的本质,那么,不关 park 和 unpark 怎么变化,都不会出错。

总结

LockSupport 本质就是二元信号量,理解了这个本质,LockSupport 就非常简单了。

附录

各种阻塞对比

比较 LockSupport.park() Object.wait() condation.await() Thread.sleep()
是否释放锁 不释放 释放锁 释放锁 不释放锁
指定等待时间 可选 可选 可选 必须
唤醒方式 unpark或超时 notify或超时 notify 或超时 自动
底层原理 UNSAFE.park native UNSAFE.park native

其他:

  1. 如果在wait()之前执行了notify()会怎样? 抛出IllegalMonitorStateException异常;

  2. 如果在park()之前执行了unpark()会怎样? 线程不会被阻塞,直接跳过park(),继续执行后续内容;

  3. 使用wait/notify实现同步时,必须先调用wait,后调用notify,如果先调用notify,再调用wait,将起不了作用。

使用 park/unpark 实现同步时,park 和 unpark 没有顺序要求。

  1. 线程 A,线程 B 启动,线程 A 先调用 park,线程 B 后调用 unpark,线程 A 在线程 B 调用 unpark 后解除阻塞。
  2. 线程 A,线程 B 启动,线程 A 调用 unpark,线程 B 先调用 park,线程 B 后调用 park 之后不会阻塞,因为 线程 A 已经调用了 unpark。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章