1、 park、unpark介绍
park、unpark它们是LockSupport类中的方法,内部调用的是Unsafe类中的native方法。
// 暂停当前线程,暂停后,线程状态变为 WAITING
LockSupport.park();
// 恢复某个线程的运行,恢复后线程状态变为RUNNING
LockSupport.unpark(要恢复的线程对象);
先park再unpark的方式是容易理解的。但还有一个场景,先unpark后再次执行park方法,也不会阻塞调用了park方法的线程。理解为park方法就是校验获取一个通行令牌,而unpark方法是获取到一个通行令牌的过程。先执行unpark方法,代表先获得了通行令牌。在另一个线程调用park方法时,校验到这个令牌存在,消耗掉这个令牌然后就可以继续往下走。
代码示例:
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
log.debug("start...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("park...");
LockSupport.park();
log.debug("resume...");
},"t1");
t1.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("unpark...");
LockSupport.unpark(t1);
}
}
输出结果:
15:45:21.606 [t1] DEBUG com.example.common.thread.TestLockSupport - start...
15:45:22.617 [t1] DEBUG com.example.common.thread.TestLockSupport - park...
15:45:56.229 [main] DEBUG com.example.common.thread.TestLockSupport - unpark...
15:49:31.797 [t1] DEBUG com.example.common.thread.TestLockSupport - resume...
2、wait/notify与park/unpark区别
- wait、notify 、notifyAll必须配合Object Monitor (即对象锁)一起使用,而park、unpark不必。
- park、unpark是以线程为单位来【阻塞】和【唤醒】线程; 而notify只能随机唤醒一个等待线程,notifyAll是唤醒所有等待线程,就不那么【精确】。
- park、unpark可以先unpark;而wait、notify不能先notify。
3、 park、unpark原理
每个线程都有自己的一个Parker对象(该Parker对象是底层的c代码实现的),由三部分组成:
_counter(代表了一个标志位)、_cond(表了内部的一个阻塞队列) 和 _mutex 。
比如:
线程就像一个旅人,Parker就像他随身携带的背包,条件变量就好比背包中的帐篷。_counter就好比背包中的干粮(0为耗尽,1为充足)。
调用park就是要看需不需要停下来歇息。如果备用干粮耗尽,那么钻进帐篷休息;如果备用干粮充足,那么不需要停留,继续前进。
调用unpark就好比令干粮充足。如果这时线程还在帐篷,就唤醒,让他继续前进;如果这时线程还在运行,那么下次他调用park时,仅是消耗备用干粮,需不要停留继续前进,因为背包空间有限,多次调用unpark仅会补充一份备用干粮。
1、当线程调用Unsafe.park()方法。
2、检查_counter ,本情况为0,这时获得_mutex互斥锁。
3、线程进入_cond条件变量阻塞。
4、设置_counter=0
1、调用Unsafe.unpark(Thread_0)方法,设置_counter为1 。
2、唤醒_cond条件变量中的Thread_0。
3、Thread_0恢复运行。
4、设置_counter为0.
1、 调用Unsafe.unpark(Thread_0)方法,设置_counter为1 。
2、当前线程调用Unsafe.park()方法。
3、检查_counter,本情况为1,这时线程无需阻塞,继续运行。
4、设置_counter为0。