Java中停止线程的原则是什么?

在 Java 中, 最好的停止线程的方式是使用中断 interrupt, 但是这仅仅是会通知到被终止的线程 "你该停止运行了", 被终止的线程自身拥有决定权 (决定是否、以及何时停止), 这依赖于请求停止方和被停止方都遵守一种约定好的编码规范.

  • 任务和线程的启动很容易. 在大多数时候, 我们都会让它们运行直到结束, 或者让它们自行停止.然而, 有时候我们希望提前结束任务或线程, 或许是因为用户取消了操作,或者服务需要被快速关闭, 或者是运行超时或出错了.

  • 要使任务和线程能安全、快速、可靠地停止下来, 并不是一件容易的事. Java没有提供任何机制来安全地终止线程. 但它提供了中断 (Interruption), 这是一种协作机制,能够使一个线程终止另一个线程的当前工作.

  • 这种协作式的方法是必要的, 我们很少希望某个任务、线程或服务立即停止,因为这种立即停止会使共享的数据结构处于不一致的状态. 相反, 在编写任务和服务时可以使用一种协作的方式: 当需要停止时,它们首先会清除当前正在执行的工作, 然后再结束. 这提供了更好的灵活性, 因为任务本身的代码比发出取消请求的代码更清楚如何执行清除工作.

  • 生命周期结束 (End-of-Lifecycle) 的问题会使任务、服务以及程序的设计和实现等过程变得复杂, 而这个在程序设计中非常重要的要素却经常被忽略. 一个在行为良好的软件与勉强运的软件之间的最主要区别就是, 行为良好的软件能很完善地处理失败、关闭和取消等过程.

处理中断的最好方法是什么?

优先选择在方法上抛出异常.

用 throws InterruptedException 标记你的方法, 不采用 try 语句块捕获异常,以便于该异常可以传递到顶层, 让run方法可以捕获这一异常, 例如:

void subTask() throws InterruptedException
	sleep(delay);
}

由于 run 方法内无法抛出 checked Exception (只能用 try catch), 顶层方法必须处理该异常, 避免了漏掉或者被吞掉的情况, 增强了代码的健壮性.

如果不能抛出中断, 要怎么做?

如果不想或无法传递 InterruptedException (例如用 run 方法的时候, 就不让该方法 throws InterruptedException), 那么应该选择在 catch 子句中调用 Thread.currentThread().interrupt() 来恢复设置中断状态, 以便于在后续的执行依然能够检查到刚才发生了中断.

代码演示详见视频, 在这里, 线程在sleep期间被中断, 并且由 catch 捕获到该中断, 并重新设置了中断状态, 以便于可以在下一个循环的时候检测到中断状态, 正常退出.

为什么用 volatile 停止线程不够全面?

解答: 这种做法是错误的, 或者说是不够全面的, 在某些情况下虽然可用, 但是某些情况下有严重问题。
这种方法在《Java并发编程实战》中被明确指出了缺陷, 我们一起来看看缺陷在哪里:

此方法错误的原因在于, 如果我们遇到了线程长时间阻塞 (这是一种很常见的情况, 例如生产者消费者模式中就存在这样的情况), 就没办法及时唤醒它, 或者永远都无法唤醒该线程, 而 interrupt 设计之初就是把 wait 等长期阻塞作为一种特殊情况考虑在内了, 我们应该用 interrupt 思维来停止线程.

Q.E.D.

知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议

Live in the future, then build what's missing.