继续学习并发~ 今天会继续学习并发方法和一种并发设计模式
1.两阶段终止模式
在一个线程T1中如何优雅终止线程T2,这里的【优雅】指的是给T2一个料理后事的机会,而不是直接停止T2。
怎么实现?
(1)错误的思路
- 使用线程对象的stop()方法停止线程
- stop方法会真正杀死线程,如果这时线程锁住了共享资源,那么当它被杀死后就再也没机会释放锁,其他线程将永远无法获取锁
(这点存疑,网上查的对stop方法的解释没有说stop会锁住共享资源,而是:stop()会释放所有的锁 stop()天生不安全,因为它会强制终止未结束的方法(包括run方法),不管run方法是否执行完了,可能会造成数据不一致问题,并且还会释放这个线程所持有的所有的锁。)
- stop方法会真正杀死线程,如果这时线程锁住了共享资源,那么当它被杀死后就再也没机会释放锁,其他线程将永远无法获取锁
- 使用System.exit(int)方法停止线程
- 目的仅是停止一个线程,这个方法会使整个程序都停止
(2)运行流程分析
while(true)监控线程的运行图如下:
- A流程:当监控线程被打断时(感知是否被打断就是看打断标记的值),就料理后事,料理完后事后就结束循环。
- B流程:当监控线程在睡眠中没有被打断,则监控线程继续循环。
- C流程:当线程在睡眠时有异常发生,线程会被打断,由之前学的知识可知,在sleep中的线程被打断其打断标记会被清除,为了能让监控线程知道自己被打断了,我们就需要手动设置打断标记,当监控线程进入下一次循环,判断自己有没有被打断时,就会看到打断标记被设置了,从而料理后事,结束循环。
(3)代码实现
@Slf4j(topic="c.Test16")
public class Test16 {
public static void main(String[] args) throws InterruptedException {
TwoPhaseTermination t = new TwoPhaseTermination();
t.start();
Thread.sleep(3000);
t.stop();
}
}
@Slf4j(topic="c.Test16")
class TwoPhaseTermination{
private Thread monitor;
public void start(){
monitor = new Thread(() -> {
//监控线程
//查看是否被打断
while (true) {
if(Thread.currentThread().isInterrupted()){
log.debug("打断了,准备后事吧");
break;
}
try {
//下面的A、B情况就是可能被打断的位置,A情况被打断打断标记会清除,所以需要我们手动设置标记
Thread.sleep(500); //A情况
log.debug("执行监控记录"); //B情况
} catch (InterruptedException e) {
e.printStackTrace();
//再打断一次打断标记就不会被清除了
Thread.currentThread().interrupt();
}
}
});
monitor.start();
}
public void stop(){
monitor.interrupt();
}
}
执行结果:
20:08:10.048 c.Test16 [Thread-0] - 执行监控记录
20:08:10.564 c.Test16 [Thread-0] - 执行监控记录
20:08:11.073 c.Test16 [Thread-0] - 执行监控记录
20:08:11.580 c.Test16 [Thread-0] - 执行监控记录
20:08:12.091 c.Test16 [Thread-0] - 执行监控记录
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.example.juc.test.TwoPhaseTermination.lambda$start$0(Test16.java:34)
at java.lang.Thread.run(Thread.java:750)
20:08:12.541 c.Test16 [Thread-0] - 打断了,准备后事吧
Process finished with exit code 0
存在一个静态方法:interrupted(),其作用与isInterrupted相同,都是判断线程是否被打断,但是其与isInterrupted不同的是,interrupted()方法会清楚打断标记。
2.interrupt打断park线程
park方法的作用就是暂停当前线程,在哪个线程中调用就对哪个线程进行操作,此线程进入wait状态(阻塞状态)。
当打断标记为true时,park方法将失效,将打断标记设为false后,park方法重新生效。
案例:
@Slf4j(topic="c.Test17")
public class Test17 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
log.debug("线程running...");
log.debug("线程被park...");
LockSupport.park();
log.debug("线程park中被interrupt...");
log.debug("线程的打断标记为{}",Thread.currentThread().isInterrupted());
});
t1.start();
Thread.sleep(1000);
t1.interrupt();
}
}
打印结果:
20:36:46.010 c.Test17 [Thread-0] - 线程running...
20:36:46.013 c.Test17 [Thread-0] - 线程被park...
20:36:47.014 c.Test17 [Thread-0] - 线程park中被interrupt...
20:36:47.014 c.Test17 [Thread-0] - 线程的打断标记为true
Process finished with exit code 0
如果打断后要再次park,需要设置打断标记:
@Slf4j(topic="c.Test17")
public class Test17 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
log.debug("线程running...");
log.debug("线程被park...");
LockSupport.park();
log.debug("线程park中被interrupt...");
log.debug("线程的打断标记为{}",Thread.interrupted());
LockSupport.park();
log.debug("park失效...");
});
t1.start();
Thread.sleep(1000);
t1.interrupt();
}
}
打印结果:
20:35:43.724 c.Test17 [Thread-0] - 线程running...
20:35:43.727 c.Test17 [Thread-0] - 线程被park...
20:35:44.724 c.Test17 [Thread-0] - 线程park中被interrupt...
20:35:44.724 c.Test17 [Thread-0] - 线程的打断标记为true
程序没有结束,而且log.debug(“park失效…”);没有执行,证明第二次park生效了。
3.现在已过时的方法
方法名 | static | 功能说明 |
---|---|---|
stop | 停止线程 | |
suspend | 挂起(暂停)线程运行 | |
resume | 恢复线程运行 |
以上方法都不推荐使用,因为会破坏同步代码块,导致锁得不到释放,从而导致线程死锁。
stop可以由interrupt方法替代
suspend可以有wait方法替代
resume可以由notify方法替代
4.守护线程
默认情况下,Java进程需要等待所有线程都运行结束,才会结束,而有一种特殊的线程叫做守护线程,只要其它非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。
setDaemon(true) 可以将线程设为守护线程
@Slf4j(topic="c.Test18")
public class Test18 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while (true){
if(Thread.currentThread().isInterrupted()){
break;
}
log.debug("t1 running");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("t1 end");
});
t1.setDaemon(true);
t1.start();
log.debug("主线程睡会儿");
Thread.sleep(1000);
log.debug("主线程醒了,主线程执行结束");
}
}
执行结果:
21:06:00.819 c.Test18 [Thread-0] - t1 running
21:06:00.819 c.Test18 [main] - 主线程睡会儿
21:06:01.331 c.Test18 [Thread-0] - t1 running
21:06:01.831 c.Test18 [Thread-0] - t1 running
21:06:01.831 c.Test18 [main] - 主线程醒了,主线程执行结束
以上案例可以看出,当给t1设置为守护线程后,当主线程执行结束后整个程序直接执行结束,没有等待守护线程t1执行结束。
需要注意的是t1.setDaemon(true)要放在t1.start()之前,否则会抛出IllegalThreadStateException异常并且setDaemon不生效。