从零开始的并发世界生活-第四天

继续学习并发~ 今天会继续学习并发方法和一种并发设计模式

1.两阶段终止模式

在一个线程T1中如何优雅终止线程T2,这里的【优雅】指的是给T2一个料理后事的机会,而不是直接停止T2。

怎么实现?

(1)错误的思路

  • 使用线程对象的stop()方法停止线程
    • stop方法会真正杀死线程,如果这时线程锁住了共享资源,那么当它被杀死后就再也没机会释放锁,其他线程将永远无法获取锁
      (这点存疑,网上查的对stop方法的解释没有说stop会锁住共享资源,而是:stop()会释放所有的锁 stop()天生不安全,因为它会强制终止未结束的方法(包括run方法),不管run方法是否执行完了,可能会造成数据不一致问题,并且还会释放这个线程所持有的所有的锁。)
  • 使用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不生效。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值