LockSupport 工具类
LockSupport 是 rt.jar 包中的一个工具类,它的主要作用就是挂起
和唤醒
线程,并且该类是创建锁和其它同步类的基础。
LockSupport 类和每个使用它的线程都会关联一个许可证
,在默认情况下调用 LockSupport 类的方法是不持有许可证的。LockSupport 是使用 Unsafe 类实现的。
下面介绍一下 LockSupport 中的主要方法。
void park()
如果在调用 park()方法之前已经获得到关联的许可证,则调用后会立即返回,否则会被禁止参与线程的调度,也就是被阻塞挂起。
如下代码,最终只会输出“开始”,然后当前线程被挂起,因为在默认情况下调用线程是不持有许可证的。
public static void main(String[] args) throws Exception { System.out.println("开始..."); LockSupport.park(); System.out.println("结束..."); } //结果: //开始...
在其它线程调用 unpark(Thread thread)方法并且将当前阻塞线程作为参数时,调用 park()方法阻塞的线程会被返回。当其它线程调用了阻塞线程的 interrupt()方法,设置中断标志或者线程被虚假唤醒,则阻塞线程也会被返回。
需要注意:
- 当调用 park()方法被阻塞的线程被其它线程中断而返回时候,并不会抛出 InterruptedException 异常。
- 许可证是一次性的。比如线程 B 连续调用了三次 unpark()方法,当线程 A 调用 park()方法就使用掉这个许可证,如果线程 A 再次调用 park()方法,则进入等待状态。
void unpark(Thread t)
当一个线程调用 unpark()方法时,如果传入的参数 thread 没有持有许可证,则让传入的 thread 持有许可证;如果 thread 之前调用 park()后被挂起,则调用 unpark()时会唤醒该 thread;如果 thread 之前没有调用 park(),则调用 unpark()方法后,再调用 park()方法时会立即返回。
public static void main(String[] args) throws Exception { System.out.println("开始..."); LockSupport.unpark(Thread.currentThread()); LockSupport.park(); System.out.println("结束..."); } //输出结果: //开始... //结束...
看如下代码,加深对 park 和 unpark 的理解。
public static void main(String[] args) throws Exception { Thread t = new Thread(() -> { System.out.println("子线程开始..."); //挂起自己 LockSupport.park(); System.out.println("子线程结束..."); }); t.start(); Thread.sleep(1000); System.out.println("主线程开始调用unpark"); LockSupport.unpark(t); } //输出结果: //子线程开始... //主线程开始调用unpark //子线程结束...
该段代码首先创建了子线程 t,子线程启动后调用 park()方法。由于默认情况下子线程没有持有许可证,则挂起自己。
主线程休眠 1 秒后,调用 unpark()并传入子线程 t,这样做就是为了让子线程获取到许可证,之后子线程中阻塞的 park()就会返回。
void parkNanos(long nanos)
和 park()方法类似,如果已经获取到了许可证则调用该方法后会马上返回。不同的是,如果没有拿到许可证,则调用线程会被挂起 nanos 时间后自动返回。
另外 park()方法还支持带有 blocker 参数的方法 void park(Object blocker)方法,当线程在没有持有许可证的情况下调用 park()方法而被阻塞挂起时,这个 blocker 对象会被记录到该线程内部。一般用于用于标识阻塞对象,该对象主要用于问题排查和系统监控。
void parkNanos(Object blocker,long nanos) 该方法相对于上个方法,增加了超时时间。
void parkUntil(Object blocker,long deadline)
public static void parkUntil(Object blocker, long deadline) { Thread t = Thread.currentThread(); setBlocker(t, blocker); UNSAFE.park(true, deadline); setBlocker(t, null); }
参数 deadline 的时间单位为 ms,这个方法和 parkNanos(Object blocker,long nanos)的区别是,后者是从当前时间等待 nanos 秒时间,而前者是指定一个时间点,比如 2022-01-19 12:00:00。
总结
与 Object 类的 wait/notify 机制相比,park/unpark 有两个优点:
- 以 thread 为操作对象更符合阻塞线程的直观定义
- 操作更精准,可以准确地唤醒某一个线程(notify 随机唤醒一个线程,notifyAll 唤醒所有等待的线程),增加了灵活性