LockSupport 工具相关整理

1. LockSupport

  • LockSupport 是用来创建锁和其他同步类的基本线程阻塞原语。
    • 是一个简单的代理类,里面的代码都是使用 Unsafe 类里面的方法。
    • JDK 对 LockSupport 的描述:Basic thread blocking primitives for creating locks and other synchronization classes。
    • 在 Java 多线程中,当需要阻塞或者唤醒一个线程时,都会使用 LockSupport 工具类来完成相应的工作。
      • LockSupport 定义了一组公共静态方法,这些方法提供了最基本的线程阻塞和唤醒功能,而 LockSupport 也因此成为了构建同步组件的基础工具。
  • LockSupport 中的 park()unpark() 的作用分别是阻塞线程和解除阻塞线程,而且 park()unpark() 不会遇到 " Thread.suspend " 和 " Thread.resume " 所可能引发的 死锁 问题。
    • 因为 park()unpark() 有许可的存在。
    • 调用 park() 的线程和另一个试图将其 unpark() 的线程之间的竞争将保持活性。

1.1 LockSupport 函数列表

// 返回提供给最近一次尚未解除阻塞的 park 方法调用的 blocker 对象,如果该调用不受阻塞,则返回 null。
static Object getBlocker(Thread t)
// 为了线程调度,禁用当前线程,除非许可可用。
static void park()
// 为了线程调度,在许可可用之前禁用当前线程。
static void park(Object blocker)
// 为了线程调度禁用当前线程,最多等待指定的等待时间,除非许可可用。
static void parkNanos(long nanos)
// 为了线程调度,在许可可用前禁用当前线程,并最多等待指定的等待时间。
static void parkNanos(Object blocker, long nanos)
// 为了线程调度,在指定的时限前禁用当前线程,除非许可可用。
static void parkUntil(long deadline)
// 为了线程调度,在指定的时限前禁用当前线程,除非许可可用。
static void parkUntil(Object blocker, long deadline)
// 如果给定线程的许可尚不可用,则使其可用。
static void unpark(Thread thread)
  • LockSupport 的核心方法是基于 Unsafe 类中的 park()unpark() 方法。
    • 变量 isAbsolute 代表传入的 time 是绝对时间还是相对时间。
    • unpark 函数为线程提供 " 许可(permit)",线程调用 park() 函数则等待 " 许可 ",有点像信号量,但是这个 " 许可 " 是不能叠加的," 许可 " 是一次性的。可以理解为设置一个变量 0,1 之间的切换。
    • 如果线程 B 连续调用了多次 unpark() 函数,当线程 A 调用 park() 函数就使用了这个 " 许可 ",如果线程 A 第二次调用 park(),则进入等待状态。
    • unpark() 函数可以先于 park() 调用。
      • 如线程 B 调用 unpark() 函数,给线程 A 一个 " 许可 ",那么当线程 A 调用 park() 时,发现已经有 " 许可 ",可以马上继续运行,不会阻塞。
//阻塞线程
public native void park(boolean isAbsolute, long time);
//取消阻塞线程
public native void unpark(Object thread);
  • 调用了 park() 方法后,会禁用当前线程,以下几种情况时,线程会被唤醒。
    • 其他某个线程将当前线程作为目标调用 unpark()。(调用 unpark 方法)
    • 其他某个线程中断当前线程。(被中断 interrupts)
    • 该调用不合逻辑地(即毫无理由地)返回。(posix condition 里的 " Spurious wakeup ")
public static void park(Object blocker) {
        //获取当前线程
        Thread t = Thread.currentThread();
        //设置线程的blocker对象
        setBlocker(t, blocker);
        //通过UNSAFE调用,挂起线程
        UNSAFE.park(false, 0L);
        //挂起的线程被唤醒以后,需要将阻塞的Blocker清理掉。
        setBlocker(t, null);
}
  • 获取当前线程,设置当前线程的 parkBlocker 字段,调用 Unsafe 类的 park() 方法,再次调用setBlocker 的原因。

    • Unsafe 的 park() 方法之后,当前线程已被阻塞。
    • unpark() 方法被调用,该线程获得许可后,继续进行下面的代码,setBlocker() 参数 parkBlocker 字段设置为 null,这样就完成了整个 park() 方法的逻辑。
  • setBlocker 修改的是 parkBlockerOffset 变量。

    • 这个变量是挂起线程对象的偏移地址,对应的是 Thread 类的 parkBlocker。
    • 这个对象是用于记录线程被阻塞时被谁阻塞,用于线程监控和分析工具定位原因。
    • parkBlocker 是在线程处于阻塞的情况下才被赋值。线程已经被阻塞,只能通过设置偏移量这种修改内存的方法来进行修改,如果调用线程内的方法,线程是不会回应调用的。
Class<?> tk = Thread.class;
parkBlockerOffset = UNSAFE.objectFieldOffset(tk.getDeclaredField("parkBlocker"));
  • 阻塞当前线程,最长等待时间不超过 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);
        }
}
  • 阻塞当前线程直到 deadline 时间,相同的,也做了阻塞前记录当前线程等待对象的操作。
public static void parkUntil(Object blocker, long deadline) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        UNSAFE.park(true, deadline);
        setBlocker(t, null);
}
  • 线程在 park() 上受阻塞,将解除其阻塞状态。
    • 否则,预发许可,下一次调用 park() 不会受阻塞。
public static void unpark(Thread thread) {
        if (thread != null)
            UNSAFE.unpark(thread);
}

1.2 与 wait()、notify() 的区别

  • 面向的主体不一样。
    • LockSupport 主要是针对 Thread 进进行阻塞处理,可以指定阻塞队列的目标对象,每次可以指定具体的线程唤醒。
    • Object.wait() 是以对象为纬度,阻塞当前的线程和唤醒单个(随机)或者所有线程。
  • 实现机制不同。
    • 虽然 LockSupport 可以指定 Monitor 的 object 对象,但和 object.wait() 两者的阻塞队列并不交叉。
  • LockSupport 阻塞和解除阻塞线程直接操作的是 Thread。而 Object 的 wait/notify 并不是直接对线程操作,是被动的方法,需要一个 Object 来进行线程的挂起或唤醒。
  • Thead 在调用 wait 之前,当前线程必须先获得该对象的监视器(syschronized),被唤醒之后需要重新获取到监视器才能继续执行。而 LockSupport 可以随意进行 park 或者 unpark。



posted @ 2020-03-26 11:51  windy杨树  阅读(207)  评论(0编辑  收藏  举报