java线程启动比较容易,但是要停止却比较麻烦。JAVA在线程设计之初设计了大量的方法来控制线程的状态,如start、suspend、resume、stop、destroy等操作。后来由于在多线程开发环境中各种并发问题而被deprecated了。本文主要介绍一些线程的stop方法以及正确的中断线程方法。
Thread.stop
在一开始使用java时,要停止一个线程,有可能会误用线程的这个方法。虽然它一开始被加入jdk是为了停止线程而用,但是stop方法是线程不安全的,且其停止原理相当粗暴,通过直接抛出一个ThreadDead的异常来释放当前线程的所有锁,并且将线程停止。这种作法会导致该线程所操作的数据不一致,比如:当前线程为了更新某个数据,而获取了某个锁,然后在更新中途,调用stop时,然后另一个线程访问该对象时,就会拿到一个数据不一致的对象,导致错误。
那么能不能通过捕获ThreadDead异常,然后进行一些操作来保证线程安全地退出呢?理论上是可以的,但是这会加大并发编程中的难度,因为有两个原因导致了这种作法不能被实际使用:
- ThreadDead异常有可能在任何地方被抛出,如果你要捕获就需要在所有地方加上catch;
- ThreadDead异常有可能被迭代抛出,导致维持数据一致的操作无法进行。
因此一般不大会用stop来进行线程的结束。那么如何进行线程的优雅结束呢?
信号变量
通过一个变更来存储线程是否结束状态,如果检测到该状态为结束状态,则停止当前线程,如下:
public class MyThread extends Thread {
private volatile boolean stoped = false;
public void done() {
stoped = true;
}
public void run() {
while (!stoped) {
// do something
}
}
}
由于是多线程处理,所以最好加上vilatile,以此保证变量可见性,虽然大多数情况下,stoped变量晚点看到问题也不大。
上面的这种方法适合于那种可以不断地去查询变量状态的情况。但是如果线程被某些操作长时间锁住,如IO阻塞,则无法响应线程的停止请求,这个时候可以使用线程中断的另一机制:Interrupt。
Thread.interrupt
Thread.interrupt方法与Thread.stop方法不同之处在于,interrupt方法只是将线程的状态改为interrupted,来等待线程自身的中断策略来进行线程中断出来,并不会粗暴的抛出一个运行期异常来中断线程。这样就解决了stop的线程不安全的问题。但是同时通过Thread.interrupt调用后并不是会肯定能够马上停止当前线程,这依赖于线程自身的中断策略。
而与通过信号变量来中断线程相比,interrupt方法被一些阻塞方法所支持,所以能够在线程被阻塞住时,也能响应interrupt。但是不是所有的阻塞操作都支持,有一些阻塞方法就不支持,如下:
-
java的socket IO。由于Inputstream和Outputstream的read和write方法不支持中断,因此在这些操作被阻塞住时可以通过socket的close方法来抛出一个socketException来中断线程;
-
java NIO的同步IO。中断其interruptibleChannel,会抛出CloseByInterruptException,通过捕获来处理中断;
-
java NIO的异步IO。通过close方法抛出的CloseSelectorException来中断。
ExecutorService的中断
线程服务的中止有两个方法,一个是shutdown,一个是shutdownNow。shutdown的实现逻辑就是将线程池的状态改为shutdown,然后再遍历所有的线程,然后调用线程的interrupt来进行所有线程的中止。而shutdownNow的不同之处在于,它会记录当前已经提交但是未结束的线程,以期能够记录下来。