一、概念
LockSupport是JDK中比较底层的类,用来创建锁和其他同步工具类的基本线程阻塞原语。
park,unpark这两个方法都是LockSupport类名下的方法,park用来暂停线程,unpark用来将暂停的线程恢复。
先park再unpark的方式是容易理解的。但还有一个场景,先unpark后再次执行park方法,也不会阻塞调用了park方法的线程。
理解为park方法就是校验获取一个通行令牌,而unpark方法是获取到一个通行令牌的过程。先执行unpark方法,代表先获得了通行令牌。那么在另一个线程调用park方法时,校验到这个令牌存在,消耗掉这个令牌然后就可以继续往下走。
二、LockSupport函数列表
public class 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()与park(Object blocker)区别说明
其他对应方法同样的原理,故以park()与park(Object blocker)为例进行说明
这两个方法都是用于线程调度的,但是有所不同:
park()
方法会将当前线程挂起(暂停),直到被唤醒或者中断。park(Object blocker)
方法与park()
方法类似,但是它还可以指定一个“阻塞器”对象,以便更好地进行调试和分析。当线程被阻塞时,阻塞器对象会被记录下来,方便程序员进行问题定位。
下面是使用代码举例:
// 示例 1: park() Thread thread1 = new Thread(() -> { System.out.println("线程开始运行"); LockSupport.park(); // 线程被挂起 System.out.println("线程被唤醒"); }); thread1.start(); Thread.sleep(3000); LockSupport.unpark(thread1); // 唤醒线程 // 示例 2: park(Object blocker) Object blocker = new Object(); Thread thread2 = new Thread(() -> { System.out.println("线程开始运行"); LockSupport.park(blocker); // 线程被挂起,并记录阻塞器对象 System.out.println("线程被唤醒"); }); thread2.start(); Thread.sleep(3000); LockSupport.unpark(thread2); // 唤醒线程 // 打印出阻塞器对象 System.out.println(LockSupport.getBlocker(thread2)); // 输出 blocker
在示例 2 中,我们使用了一个阻塞器对象 blocker,当线程被阻塞时,该对象会被记录下来。在最后一行代码中,我们打印出了线程 thread2 的阻塞器对象,输出结果为 "blocker"。
三、基本使用
- 先 park 再 unpark
import lombok.extern.slf4j.Slf4j; import java.util.concurrent.locks.LockSupport; import static site.weiyikai.concurrent.utils.Sleeper.sleep; /** * Created by xiaowei * Date 2022/10/22 * Description 先 park 再 unpark */ @Slf4j(topic = "c.Test01") public class Test01 { public static void main(String[] args) { Thread t1 = new Thread(() -> { log.debug("start..."); sleep(1); log.debug("park..."); LockSupport.park(); log.debug("resume..."); },"t1"); t1.start(); sleep(2); log.debug("unpark..."); LockSupport.unpark(t1); } }
输出结果
21:16:35.017 c.Test01 [t1] - start... 21:16:36.019 c.Test01 [t1] - park... 21:16:37.017 c.Test01 [main] - unpark... 21:16:37.017 c.Test01 [t1] - resume...
- 先 unpark 再 park
import lombok.extern.slf4j.Slf4j; import java.util.concurrent.locks.LockSupport; import static site.weiyikai.concurrent.utils.Sleeper.sleep; /** * Created by xiaowei * Date 2022/10/22 * Description 先 unpark 再 park */ @Slf4j(topic = "c.Test02") public class Test02 { public static void main(String[] args) { Thread t1 = new Thread(() -> { log.debug("start..."); sleep(2); log.debug("park..."); LockSupport.park(); log.debug("resume..."); },"t1"); t1.start(); sleep(1); log.debug("unpark..."); LockSupport.unpark(t1); } }
输出结果
21:18:03.732 c.Test02 [t1] - start... 21:18:04.731 c.Test02 [main] - unpark... 21:18:05.735 c.Test02 [t1] - park... 21:18:05.735 c.Test02 [t1] - resume...
与Object的wait¬ify对比
- wait,notify 和 notifyAll 必须配合 Object Monitor 一起使用,而 park,unpark 不必;
- park & unpark 是以线程为单位来【阻塞】和【唤醒】线程,而 notify 只能随机唤醒一个等待线程,notifyAll是唤醒所有等待线程,就不那么【精确】;
- park & unpark 可以先 unpark,而 wait & notify 不能先 notify。
四、原理
每个线程都会关联一个 Parker 对象,每个 Parker 对象都各自维护了三个角色:_counter
(计数器)、 _mutex
(互斥量)、_cond
(条件变量)。
4.1 情况一:先调用park,再调用unpark
park 操作
- 当前线程调用 Unsafe.park() 方法
- 检查 counter ,本情况为 0,这时,获得 mutex 互斥锁
- 线程进入 _cond 条件变量阻塞
- 设置 _counter = 0
unpark 操作
- 调用 Unsafe.unpark(Thread_0) 方法,设置 _counter 为 1
- 唤醒 _cond 条件变量中的 Thread_0
- Thread_0 恢复运行
- 设置 _counter 为 0
4.2 情况二:先调用unpark,再调用park
- 调用 Unsafe.unpark(Thread_0) 方法,设置 _counter 为 1
- 当前线程调用 Unsafe.park() 方法
- 检查 _counter ,本情况为 1,这时线程无需阻塞,继续运行
- 设置 _counter 为 0