在之前的文章中 我们介绍了线程的创建和终止,从源码的角度去理解了其中的细节,那么现在如果面试有人问你 “如何优雅的停止一个线程?”, 你该如何去回答尼 ?能不能完美的回答尼?
对于线程的停止,通常情况下我们是不会去手动去停止的,而是等待线程自然运行至结束停止,但是在我们实际开发中,会有很多情况中我们是需要提前去手动来停止线程,比如程序中出现异常错误,比如使用者关闭程序等情况中。在这些场景下如果不能很好地停止线程那么就会导致各种问题,所以正确的停止程序是非常的重要的。
强行停止线程会怎样?
在我们平时的开发中我们很多时候都不会注意线程是否是健壮的,是否能优雅的停止,很多情况下都是贸然的强制停止正在运行的线程,这样可能会造成一些安全问题,为了避免造成这种损失,我们应该给与线程适当的时间来处理完当前线程的收尾工作, 而不至于影响我们的业务。
对于 Java 而言,最正确的停止线程的方式是使用 interrupt。但 interrupt 仅仅起到通知被停止线程的作用。而对于被停止的线程而言,它拥有完全的自主权,它既可以选择立即停止,也可以选择一段时间后停止,也可以选择压根不停止。可能很多同学会疑惑,既然这样那这个存在的意义有什么尼,其实对于 Java 而言,期望程序之间是能够相互通知、协作的管理线程
比如我们有线程在进行 io 操作时,当程序正在进行写文件奥做,这时候接收到终止线程的信号,那么它不会立马停止,它会根据自身业务来判断该如何处理,是将整个文件写入成功后在停止还是不停止等都取决于被通知线程的处理。如果这里立马终止线程就可能造成数据的不完整性,这是我们业务所不希望的结果。
interrupt 停止线程关于 interrupt 的使用我们不在这里过多阐述,可以看 文中的介绍,其核心就是通过调用线程的 isInterrupt() 方法进而判断中断信号,当线程检测到为 true 时则说明接收到终止信号,此时我们需要做相应的处理
我们编写一个简单例子来看
Thread thread = new Thread(() -> { while (true) { //判断当前线程是否中断, if (Thread.currentThread().isInterrupted()) { System.out.println("线程1 接收到中断信息,中断线程...中断标记:" + Thread.currentThread().isInterrupted()); //跳出循环,结束线程 break; } System.out.println(Thread.currentThread().getName() + "线程正在执行..."); } }, "interrupt-1"); //启动线程 1 thread.start(); //创建 interrupt-2 线程 new Thread(() -> { int i = 0; while (i <20){ System.out.println(Thread.currentThread().getName()+"线程正在执行..."); if (i == 8){ System.out.println("设置线程中断...." ); //通知线程1 设置中断通知 thread.interrupt(); } i ++; try { TimeUnit.MILLISECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } },"interrupt-2").start();上述代码相对比较简单,我们创建了两个线程,第一个线程我们其中做了中断信号检测,当接收到中断请求则结束循环,自然的终止线程,在线程二中,我们模拟当执行到 i==8 时通知线程一终止,这种情况下我们可以看到程序自然的进行的终止。
这里有个思考: 当处于 sleep 时,线程能否感受到中断信号?
对于这一特殊情况,我们可以将上述代码稍微修改即可进行验证,我们将线程1的代码中加入 sleep 同时让睡眠时间加长,让正好线程2通知时线程1还处于睡眠状态,此时观察是否能感受到中断信号
//创建 interrupt-1 线程 Thread thread = new Thread(() -> { while (true) { //判断当前线程是否中断, if (Thread.currentThread().isInterrupted()) { System.out.println("线程1 接收到中断信息,中断线程...中断标记:" + Thread.currentThread().isInterrupted()); Thread.interrupted(); // //对线程进行复位,由 true 变成 false System.out.println("经过 Thread.interrupted() 复位后,中断标记:" + Thread.currentThread().isInterrupted()); //再次判断是否中断,如果是则退出线程 if (Thread.currentThread().isInterrupted()) { break; } break; } System.out.println(Thread.currentThread().getName() + "线程正在执行..."); try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } } }, "interrupt-1");我们执行修改后的代码,发现如果 sleep、wait 等可以让线程进入阻塞的方法使线程休眠了,而处于休眠中的线程被中断,那么线程是可以感受到中断信号的,并且会抛出一个 InterruptedException 异常,同时清除中断信号,将中断标记位设置成 false。这样一来就不用担心长时间休眠中线程感受不到中断了,因为即便线程还在休眠,仍然能够响应中断通知,并抛出异常。