虽然Thread.stop和suspend等方法提供了线程终止的机制,但由于存在一些严重的缺陷,因此应该避免使用。可以说Java并没有提供任何机制来安全地终止线程。但它提供了中断(Interrupt),这时一种协作机制,能够使一个线程终止另一个线程的当前工作。
提到中断,我们就不得不说明一下Thead.interrupt方法:调用interrupt并不意味着立即停止目标线程正在进行的工作,而只是传递了请求中断的消息。
请看下面一个程序:
Thread t = new Thread(new Runnable(){ @Override public void run() { long i = 0; while(true) { System.out.println(i++); try { Thread.sleep(1000); } catch (InterruptedException e) { System.out.println(Thread.currentThread().isInterrupted()); Thread.currentThread().interrupt(); System.out.println(Thread.currentThread().isInterrupted()); e.printStackTrace(); } } } }); t.start(); try { Thread.sleep(3000); t.interrupt(); } catch (InterruptedException e) { e.printStackTrace(); }
哪怕在主线程中调用了t.interrupt()这个方法,但是t线程并不会停止执行,还是会无线执行下去。因为这个方法只是发送了一个消息给t线程,而t线程内部也只是将该线程的状态设置为了中断,这个状态可以通过Thead.isInterrupted或者静态方法interrupted来得到(如果已经中断,那么得到的是true)。
对中断操作的正确理解是:它并不会真正地中断一个正在运行的线程,而只是发出中断请求,然后由线程在下一个合适的时刻中断自己。
关于中断有两个地方需要注意:
- 一是捕获了InterruptedException异常,在JVM内部捕获到该异常以后会马上清除中断状态,也就是说调用Thead.isInterrupted得到将会是false
- 二是调用了静态方法interrupted,这个静态方法也会清除中断状态,如果仅仅要得到当前的状态信息,请使用Thead.isInterrupted
需要注意:只有当线程在阻塞状态下发生中断时,才会抛出InterruptedException异常,这里所说的阻塞状态包括三种情况:
- wait
- sleep
- join
在非阻塞状态下中断时,仅仅它的中断状态会被设置,并不会触发InterruptedException异常,这里和别的异常不一样,你要是在非阻塞状态下手工捕获这个异常,编译是通不过的。会提示:Unreachable catch block for InterruptedException. This exception is never thrown from the try statement body。 而捕获别的异常不会有这种情况。
这时候你就要在线程中自己来判断中断状态,比如下面这个程序,在每次循环的开始部分都判断当前的中断状态,当状态改变时,线程结束:
Runnable task = new Runnable() { @Override public void run() { while(!Thread.currentThread().isInterrupted()) { System.out.println(1); } } };
通常,中断是实现取消的最合理方式,不要使用boolean标志来请求取消。
有人会说了,interrupt方法是Thread的方法,而我们在使用Executor框架执行任务的时候经常会使用Runnable。这时,我们就无法使用中断方法来取消了。这个时候我们可以使用Future来实现取消,查看API你会发现Future有一个cancel方法,再去查看源码,下面是cancle方法的内部实现:
boolean innerCancel(boolean mayInterruptIfRunning) { for (;;) { int s = getState(); if (ranOrCancelled(s)) return false; if (compareAndSetState(s, CANCELLED)) break; } if (mayInterruptIfRunning) { Thread r = runner; if (r != null) r.interrupt(); } releaseShared(0); done(); return true; }
我们可以发现,它也是通过中断线程来实现的,所以要真正的取消任务还是要在每个任务中实现自己的中断策略。
因此:由于每个线程拥有各自的中断策略,因此除非你直到中断对该线程的含义,否则就不应该中断这个线程。没有实现中断策略的任务哪怕你调了cancel方法也是无法中断这个线程的。比如下面这个非阻塞任务(阻塞任务必定会抛出InterruptedException,一般都会实现中断策略的):
ExecutorService executorService = Executors.newFixedThreadPool(10); Runnable task = new Runnable() { @Override public void run() { while(true) { System.out.println(1); } } }; Future<?> future = executorService.submit(task); Thread.sleep(2000); future.cancel(true);
这个程序哪怕被cancel了,其实也是一直会print出字符的。
你可以这么写,在规定的三秒钟之内没有执行完任务(这个程序只要不中断,必然不会执行完,是一个死循环),那么主线程就会取消它:ExecutorService executorService = Executors.newFixedThreadPool(10); Runnable task = new Runnable() { @Override public void run() { while(!Thread.currentThread().isInterrupted()) { System.out.println(1); } } }; Future<?> future = executorService.submit(task); try { future.get(3, TimeUnit.SECONDS); } catch (ExecutionException e) { e.printStackTrace(); } catch (TimeoutException e) { e.printStackTrace(); } finally{ future.cancel(true); }