停止线程的方法
如何停止一个正在执行的线程?
Thread#stop()
java.lang.Thread#stop:强制线程中止执行。
从JDK1.2开始,该API已被弃用,由于它可能导致线程安全问题。
Thread#stop()方法通过抛出java.lang.ThreadDeath 异常来达到停止线程的目的。这会使线程释放它持的有一律锁,假如之前被锁保护的对象已经处于不一致状态,那么这些状态将立即对其它线程可见。当其它线程操作一个损坏的对象时,将会导致不可预测的后果!
而且,默认情况下,ThreadDeath错误不会被打印或者通知应用程序。在 java.lang.ThreadGroup中,可以看到这样一段代码:
public void uncaughtException(Thread t, Throwable e) { if (parent != null) { parent.uncaughtException(t, e); } else { Thread.UncaughtExceptionHandler ueh = Thread.getDefaultUncaughtExceptionHandler(); if (ueh != null) { ueh.uncaughtException(t, e); } // ThreadDeath异常被忽略 else if (!(e instanceof ThreadDeath)) { // 调用Thread#stop()方法时,可在此处断点调试 System.err.print("Exception in thread \"" + t.getName() + "\" "); e.printStackTrace(System.err); } }}这意味着,假如你使用stop()方法停止线程,你的应用不会收到任何通知。此时,你的某些对象可能已经处于不一致状态,然而你对此一无所知。
是否通过捕获ThreadDeath异常来解决这种情况呢?理论上是可以的,但是不推荐这么做。起因有二:
ThreadDeath异常可能在线程执行的任何地方抛出。那么,所有的同步方法和同步代码块都需要对ThreadDeath异常捕获解决。- 在
catch或者finally子句中解决第一个ThreadDeath异常时,可能会有第二个ThreadDeath抛出,必需重复解决直到成功。
这样做的代价太大,不现实。
既然Thread#stop()已经被弃用了,那么有没有什么替代方案可以停止线程呢?
基于状态的信号机制
在大部分使用Thread#stop()的地方都可以用基于状态的信号机制来替代。
在线程之间共享一个变量,目标线程定期地去检查这个变量的状态并据此判断能否应该继续执行,而其它线程即可以通过修改这个状态来停止目标线程。
public class StatusBasedSignalStop { private static volatile boolean isRunning = true; public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new Runnable() { public void run() { // 检查变量的值,判断能否应该停止执行 while (isRunning) { System.out.println("线程状态: " + Thread.currentThread().getState()); try { Thread.sleep(1100); } catch (InterruptedException e) { e.printStackTrace(); } } // 退出执行前可以做少量善后工作,比方资源清除 } }); thread.start(); // 3s后修改变量的值,表明目标线程应该停止了 Thread.sleep(3000); isRunning = false; // 休眠1s,再看目标线程的状态 Thread.sleep(1000); System.out.println("线程状态: " + thread.getState()); }}程序输出如下:
线程状态: RUNNABLE线程状态: RUNNABLE线程状态: RUNNABLE线程状态: TERMINATED注意:被线程共享的变量必需使用
volatile关键字修饰,或者者在同步方法或者同步代码块中操作,这样才能保证变量的修改对其它线程可见。
Thread#interrupt()
基于状态的信号机制可以满足大部分线程停止场景,但是当目标线程处于长时间等待状态(比方在一个条件上等待)时,该机制无法使用。此时,可以尝试使用Thread#interrupt()去打断它。
public class InterruptStop { public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new Runnable() { public void run() { try { // 我想睡好久好久 Thread.sleep(Long.MAX_VALUE); } catch (InterruptedException ex) { ex.printStackTrace(); // Thread#sleep()抛出InterruptedException会清理中断标志,此处再次中断线程 Thread.currentThread().interrupt(); } } }); thread.start(); // 主线程休眠1s,等待目标线程进入睡眠 Thread.sleep(1000); // 调用目标线程的interrupt()方法中断等待 thread.interrupt(); // 等待目标线程执行完毕 thread.join(); }}运行main()函数,1秒后主线程退出。这说明Thread#interrupt()成功停止了目标线程的睡眠。
然而,并不是所有的等待都被Thread#interrupt停止,当目标线程处于以下等待场景时:
- Object#wait();
- Object#wait(long);
- Object#wait(long, int);
- Thread#join();
- Thread#join(long);
- Thread#sleep(long);
- Thread#sleep(long, int);
- 在
java.nio.channels.InterruptibleChannel上的阻塞IO操作; - 在
java.nio.channels.Selector上的阻塞IO操作;
调用Thread#interrupt方法会中断等待(不同的情景响应中断后线程的中断标志不同)。其余情况下调用Thread#interrupt只会设置目标线程的中断标志,无法停止等待状态。此时,只能根据具体场景来分析如何停止等待(比方,假如线程在Socket上长时间等待,可以关闭Socket来停止等待)。