一、stop()方法
说到中断线程,我们首先会想到用stop()方法,但是这个方法已经被官方废弃了,下面是官方的解释:
《Why are Thread.stop, Thread.suspend and Thread.resume Deprecated?》
为什么弃用stop:
- 调用 stop() 方法会立刻停止 run() 方法中剩余的全部工作,包括在 catch 或 finally 语句中的,并抛出ThreadDeath异常(通常情况下此异常不需要显示的捕获),因此可能会导致一些清理性的工作的得不到完成,如文件,数据库等的关闭。
- 调用 stop() 方法会立即释放该线程所持有的所有的锁,导致数据得不到同步,出现数据不一致的问题。
所以我们想要中断线程就不能够使用stop()方法。
二、interrupt()方法
使用interrupt()方法真的就是中断线程了吗?并不是!此方法并不是像stop()一样暴力的中断了线程的执行,只是会给线程一个标志,告诉线程可以进行中断了,但何时中断是由线程自己控制的。
下面让我们看下interrupt()的例子:
public class InterruptedTask implements Runnable{
@Override
public void run() {
Thread currentThread = Thread.currentThread();
while (true){
if(currentThread.isInterrupted()){
break;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class InterruptedTest {
public static void main(String[] args){
InterruptedTask interruptedTask = new InterruptedTask();
Thread interruptedThread = new Thread(interruptedTask);
interruptedThread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
interruptedThread.interrupt();
}
}
运行结果如上,可以看出线程并没有退出,为什么会这样呢???
Thread.sleep() 方法会抛出一个 InterruptedException 异常,当线程被 sleep() 休眠时,如果被中断,这会就抛出这个异常。并且Thread.sleep() 方法由于中断而抛出的异常,是会清除中断标记的,”currentThread.isInterrupted()“就变为false了,所以就永远不会中断线程了,那我们应该如何来中断线程呢?
问题解决
正确的处理方式应该是在InterruptedTask类中的run()方法中的while(true)循环中捕获异常之后重新设置中断标志位,所以,正确的InterruptedTask类的代码如下所示。
public class InterruptedTask implements Runnable{
@Override
public void run() {
Thread currentThread = Thread.currentThread();
while (true){
if(currentThread.isInterrupted()){
break;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
currentThread.interrupt();
}
}
}
}
可以看到,我们在捕获InterruptedException异常的catch代码块中新增了一行代码。
currentThread.interrupt();
这就使得我们捕获到InterruptedException异常后,能够重新设置线程的中断标志位,从而中断当前执行的线程。
我们再次运行InterruptedTest类的main方法,如下所示。
可以看出线程已经被正确的中断了。
总结
处理InterruptedException异常时要小心,如果在调用执行线程的interrupt()方法中断执行线程时,抛出了InterruptedException异常,则在触发InterruptedException异常的同时,JVM会同时把执行线程的中断标志位清除,此时调用执行线程的isInterrupted()方法时,会返回false。此时,正确的处理方式是在执行线程的run()方法中捕获到InterruptedException异常,并重新设置中断标志位(也就是在捕获InterruptedException异常的catch代码块中,重新调用当前线程的interrupt()方法)。
有没有更加优雅并且我们可以主动控制的中断线程的方法呢?有!使用标志位终止线程。
三、标志位中断线程
有时候,我们希望自己可以主动控制线程的中断,而不是由线程自己来控制,这时我们就可以使用自定义的标志位来控制线程是否中断,比如在服务端程序中可能会使用 while(true) { ... }
这样的循环结构来不断的接收来自客户端的请求。此时就可以用修改标志位的方式来结束 run() 方法。
public class ServerThread extends Thread {
//volatile修饰符用来保证其它线程读取的总是该变量的最新的值
public volatile boolean exit = false;
@Override
public void run() {
ServerSocket serverSocket = new ServerSocket(8080);
while(!exit){
serverSocket.accept(); //阻塞等待客户端消息
...
}
}
public static void main(String[] args) {
ServerThread t = new ServerThread();
t.start();
...(进行一些其他逻辑操作)
t.exit = true; //修改标志位,退出线程
}
}
由上面代码可以看出,在main()方法中进行了一些列逻辑判处断后,设置exit为true时,run方法中的while(!exit){…}循环变为false,此时就退了run方法,中断了该线程。
这样就优雅并且主动的由我们控制何时来中断线程,可以更好的满足我们的业务需求,而不是由线程本身去控制,因为有时候对于我们可能是不可控的,主动权还是要掌握在我们自己手中的。
总结
上面总结了三种中断线程的方法,第一种stop()方法已经被官方废弃,不推荐使用。第二种interrupt()方法是设置线程标志位,但是何时中断是由线程自己控制的,主动权不在我们自己手中。第三种设置中断标志位,这种方法可以有我们主动控制,可以根据业务需求,选着何时中断线程,这样更满足我们业务的定制化需求。