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()
时,发现已经有 " 许可 ",可以马上继续运行,不会阻塞。
- 如线程 B 调用
//阻塞线程 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()
方法的逻辑。
- Unsafe 的
-
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。