虽然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);
}