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.parknativeUNSAFE.parknative

其他:

  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。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值