1、基本使用
他们都是 LockSupport 工具类提供的方法,需要先 park 然后再 unpark
// 暂停当前线程
LockSupport.park();
// 恢复某个线程的运行
LockSupport.unpark(暂停线程对象)
@SneakyThrows
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
log.info("start...");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("park...");
LockSupport.park();
log.info("resume...");
}, "t1");
t1.start();
TimeUnit.SECONDS.sleep(2);
log.info("unpark...");
LockSupport.unpark(t1);
}
如果先 unpark 再 park,那么 park 将会失效
@SneakyThrows
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
log.info("start...");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("park...");
LockSupport.park();
log.info("resume...");
}, "t1");
t1.start();
TimeUnit.SECONDS.sleep(1);
log.info("unpark...");
LockSupport.unpark(t1);
}
2、对比wait & notify
- wait,notify 和 notifyAll 必须配合 Object Monitor (锁)一起使用,而 park,unpark 不必
- park & unpark 是以线程为单位来【阻塞】和【唤醒】线程,而 notify 只能随机唤醒一个等待线程,notifyAll 是唤醒所有等待线程,就不那么【精确】
- park & unpark 可以先 unpark,而 wait & notify 不能先 notify
3、原理
每个线程都有自己的一个 Parker 对象,由三部分组成 _counter , _condition 和 _mutex(互斥锁),由底层 c 实现,JAVA 层面不可见,好多 JUC 底层实现都用到了它,打个比喻
- 线程就像一个旅人,Parker 就像他随身携带的背包,条件变量 _condition 就好比背包中的帐篷。_counter 就好比背包中的备用干粮(0 为耗尽,1 为充足,初始为0),调用park 就是旅人饿了,如果备用干粮耗尽,那么钻进帐篷歇息,否则继续前进,调用 unpark,就好比令干粮充足,因为背包空间有限,多次调用 unpark 仅会补充一份备用干粮,如果这时线程因为饿了还在帐篷,就唤醒让他吃东西继续前进,如果这时线程还在运行,那么下次他调用 park 时,仅是消耗掉备用干粮,不需停留继续前进
- 也就是说,_counter 一开始等于 0 ,当调用 park ,会先获得 _mutex 互斥锁,然后阻塞,直到调用 unpark 补充干粮,然后唤醒继续上路,如果一开始还没饿,也就是还没有调用 park,先调用 unpark 补充干粮,那么 _counter 就先变为 1,等到再执行 park 的时候,发现还有干粮,就不阻塞了,直接运行