4.8Park&UnPark
基本使用
- 它们是 LockSupport 类中的方法
// 暂停当前线程
LockSupport.park();
// 恢复某个线程的运行
LockSupport.unpark(暂停线程对象)
先 park 再 unpark
@Slf4j
public class ParkTest {
public static void main(String[] args) throws InterruptedException {
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();
Thread.sleep(2000);
log.debug("unPark...");
LockSupport.unpark(t1); // 叫醒t1线程(t1线程先进入了park)
}
}
/*输出结果*/
/*
16:16:30.036 [t1] DEBUG com.zzhua.test11.ParkTest - start...
16:16:31.040 [t1] DEBUG com.zzhua.test11.ParkTest - park...
16:16:32.034 [main] DEBUG com.zzhua.test11.ParkTest - unPark...
16:16:32.034 [t1] DEBUG com.zzhua.test11.ParkTest - resume...
*/
先 unpark 再 park
@Slf4j
public class ParkTest {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
log.debug("start...");
try {
Thread.sleep(2000); // 睡眠2秒,目的是测试先进行unPark
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("park...");
LockSupport.park();
log.debug("resume...");
}, "t1");
t1.start(); // 注意这里是在t1线程调用了start方法后, 再unPark t1的
// 如果再t1线程调用star方法之前,调用unpark t1, 结果又不一样了
Thread.sleep(1000);
log.debug("unPark...");
LockSupport.unpark(t1); // 这里先unPark, 在t1线程将来调用LockSupport.park()时,忽略并直接运行(看输出)
// 提前unPark只能消除将来的一次park,如果将来>=2次调用LockSupport.park()
} // 那么,会park
} // 提前多次调用UnPark也只能消除将来的一次park,如果将来>=2次调用
// LockSupport.park(),也会park
/*输出结果*/
/*
16:18:32.653 [t1] DEBUG com.zzhua.test11.ParkTest - start...
16:18:33.651 [main] DEBUG com.zzhua.test11.ParkTest - unPark...
16:18:34.656 [t1] DEBUG com.zzhua.test11.ParkTest - park...
16:18:34.656 [t1] DEBUG com.zzhua.test11.ParkTest - resume...
*/
再来看两个示例
涉及到LockSupport与start方法调用先后的关系,
通过这两个示例可以得出结论:我们可以使用LockSupport.unPark(指定线程)方法来精确的唤醒某个已经开启了的线程
@Slf4j
public class ParkTest {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
log.debug("t1进入park");
LockSupport.park();
log.debug("t1继续运行");
});
t1.start(); // 先start t1,再unPark
LockSupport.unpark(t1);
log.debug("main...");
}
}
/*输出结果*/
/*
17:25:49.286 [main] DEBUG com.zzhua.test11.ParkTest - main...
17:25:49.286 [Thread-0] DEBUG com.zzhua.test11.ParkTest - t1进入park
17:25:49.288 [Thread-0] DEBUG com.zzhua.test11.ParkTest - t1继续运行
*/
@Slf4j
public class ParkTest {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
log.debug("t1进入park");
LockSupport.park();
log.debug("t1继续运行");
});
LockSupport.unpark(t1); // 在调用t1的start方法之前, 先调用unPark , 再调用start
t1.start(); // 通过输出结果可以看出,t1没有继续运行,
log.debug("main..."); // 所以LockSupport应该在调用start方法之后被调用
}
}
/*输出结果*/// 线程停住,没有结束
/*
17:26:57.797 [main] DEBUG com.zzhua.test11.ParkTest - main...
17:26:57.797 [Thread-0] DEBUG com.zzhua.test11.ParkTest - t1进入park
*/
特点
- 与 Object 的 wait & notify 相比
- wait,notify 和 notifyAll 必须配合 Object Monitor 一起使用,而 park,unpark 不必
- park & unpark 是以线程为单位来【阻塞】和【唤醒】线程,而 notify 只能随机唤醒一个等待线程,notifyAll是唤醒所有等待线程,就不那么【精确】
- park & unpark 可以先 unpark,而 wait & notify 不能先 notify
*原理之Park&unPark
每个线程都有自己的一个 Parker 对象,由三部分组成 _counter , _cond 和 _mutex 打个比喻
- 线程就像一个旅人,Parker 就像他随身携带的背包,条件变量就好比背包中的帐篷。_counter 就好比背包中的备用干粮(0 为耗尽,1 为充足)
- 调用 park, 就是要看需不需要停下来歇息
- 如果备用干粮耗尽,那么钻进帐篷歇息
- 如果备用干粮充足,那么不需停留,继续前进,并消耗干料
- 调用 unpark,就好比令干粮充足
- 如果这时线程还在帐篷,就唤醒让他继续前进,并消耗干粮
- 如果这时线程还在运行,那么下次他调用 park 时,仅是消耗掉备用干粮,不需停留继续前进,因为背包空间有限,多次调用 unpark 仅会补充一份备用干粮