一、概念
LockSupport是JDK中比较底层的类,用来创建锁和其他同步工具类的基本线程阻塞原语。
park,unpark这两个方法都是LockSupport类名下的方法,park用来暂停线程,unpark用来将暂停的线程恢复。
先park再unpark的方式是容易理解的。但还有一个场景,先unpark后再次执行park方法,也不会阻塞调用了park方法的线程。
理解为park方法就是校验获取一个通行令牌,而unpark方法是获取到一个通行令牌的过程。先执行unpark方法,代表先获得了通行令牌。那么在另一个线程调用park方法时,校验到这个令牌存在,消耗掉这个令牌然后就可以继续往下走。
二、LockSupport函数列表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public class LockSupport {
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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| Thread thread1 = new Thread(() -> { System.out.println("线程开始运行"); LockSupport.park(); System.out.println("线程被唤醒"); }); thread1.start();
Thread.sleep(3000); LockSupport.unpark(thread1);
Object blocker = new Object(); Thread thread2 = new Thread(() -> { System.out.println("线程开始运行"); LockSupport.park(blocker); System.out.println("线程被唤醒"); }); thread2.start();
Thread.sleep(3000);
System.out.println(LockSupport.getBlocker(thread2));
LockSupport.unpark(thread2);
System.out.println(LockSupport.getBlocker(thread2));
|
输出结果:
1 2 3 4 5 6
| 线程开始运行 线程被唤醒 线程开始运行 java.lang.Object@4dd8dc3 线程被唤醒 null
|
三、基本使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.LockSupport;
import static site.weiyikai.concurrent.utils.Sleeper.sleep;
@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); } }
|
输出结果
1 2 3 4
| 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...
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.LockSupport;
import static site.weiyikai.concurrent.utils.Sleeper.sleep;
@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); } }
|
输出结果
1 2 3 4
| 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-notify对比
- wait,notify 和 notifyAll 必须配合 Object Monitor 一起使用,而 park,unpark 不必;
- park & unpark 是以线程为单位来【阻塞】和【唤醒】线程,而 notify 只能随机唤醒一个等待线程,notifyAll是唤醒所有等待线程,就不那么【精确】;
- park & unpark 可以先 unpark,而 wait & notify 不能先 notify。
中断响应
使用interrupt方法去打断唤醒线程,注意打断 park 线程, 不会清空打断状态,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public static void main(String[] args) throws InterruptedException { test3(); }
private static void test3() { Thread t1 = new Thread(() -> { log.debug("park..."); LockSupport.park(); log.debug("unpark..."); log.debug("打断状态:{}", Thread.currentThread().isInterrupted()); }, "t1"); t1.start();
sleep(0.5); t1.interrupt(); }
|
结果:
1 2 3
| 10:47:53.023 c.TestInterrupt [t1] - park... 10:47:53.535 c.TestInterrupt [t1] - unpark... 10:47:53.535 c.TestInterrupt [t1] - 打断状态:true
|
如果打断标记已经是 true, 则 park 会失效
1 2 3 4 5 6 7 8 9 10 11 12
| private static void test4() { Thread t1 = new Thread(() -> { for (int i = 0; i < 5; i++) { log.debug("park..."); LockSupport.park(); log.debug("打断状态:{}", Thread.currentThread().isInterrupted()); } }); t1.start(); sleep(1); t1.interrupt(); }
|
输出
1 2 3 4 5 6 7 8 9 10
| 21:13:48.783 [Thread-0] c.TestInterrupt - park... 21:13:49.809 [Thread-0] c.TestInterrupt - 打断状态:true 21:13:49.812 [Thread-0] c.TestInterrupt - park... 21:13:49.813 [Thread-0] c.TestInterrupt - 打断状态:true 21:13:49.813 [Thread-0] c.TestInterrupt - park... 21:13:49.813 [Thread-0] c.TestInterrupt - 打断状态:true 21:13:49.813 [Thread-0] c.TestInterrupt - park... 21:13:49.813 [Thread-0] c.TestInterrupt - 打断状态:true 21:13:49.813 [Thread-0] c.TestInterrupt - park... 21:13:49.813 [Thread-0] c.TestInterrupt - 打断状态:true
|
提示
可以使用 Thread.interrupted() 清除打断状态
四、原理
每个线程都会关联一个 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