Eric Goebelbecker 2024-09-07
1. 引言
在本简短文章中,我们将介绍如何在 Java 中停止一个线程——这并不简单,因为 Thread.stop() 方法已被弃用。
正如 Oracle 的这篇更新说明 所解释的那样,stop() 方法可能导致被监控对象的状态被破坏(即对象处于不一致或损坏状态)。
2. 使用标志位(Flag)
我们首先创建一个类,用于启动并管理一个线程。该任务本身不会自动结束,因此我们需要一种方式来安全地停止该线程。
为此,我们将使用一个原子布尔标志(AtomicBoolean):
public class ControlSubThread implements Runnable {
private Thread worker;
private final AtomicBoolean running = new AtomicBoolean(false);
private int interval;
public ControlSubThread(int sleepInterval) {
interval = sleepInterval;
}
public void start() {
worker = new Thread(this);
worker.start();
}
public void stop() {
running.set(false);
}
public void run() {
running.set(true);
while (running.get()) {
try {
Thread.sleep(interval);
} catch (InterruptedException e){
Thread.currentThread().interrupt();
System.out.println(
"线程被中断,未能完成操作");
}
// 在此处执行某些任务
}
}
}
这里我们没有使用一个恒为 true 的 while 循环,而是使用了 AtomicBoolean,这样就可以通过将其设为 true 或 false 来控制线程的启动与停止。
正如我们在《原子变量简介》一文中所解释的,使用 AtomicBoolean 可以避免多个线程在设置和读取该变量时发生冲突。
3. 中断线程(Interrupting a Thread)
如果 sleep() 的时间间隔设置得很长,或者线程正在等待一个可能永远不会释放的锁,会发生什么情况?
这时就存在长时间阻塞甚至无法干净终止的风险。
为应对这些情况,我们可以使用 interrupt() 方法。让我们在类中添加几个方法和一个新的标志:
public class ControlSubThread implements Runnable {
private Thread worker;
private AtomicBoolean running = new AtomicBoolean(false);
private AtomicBoolean stopped = new AtomicBoolean(false);
private int interval;
// ...
public void interrupt() {
running.set(false);
worker.interrupt();
}
boolean isRunning() {
return running.get();
}
boolean isStopped() {
return stopped.get();
}
public void run() {
running.set(true);
stopped.set(false);
while (running.get()) {
try {
Thread.sleep(interval);
} catch (InterruptedException e){
Thread.currentThread().interrupt();
System.out.println(
"线程被中断,未能完成操作");
}
// 执行某些任务
}
stopped.set(true);
}
}
我们新增了一个 interrupt() 方法,它会将 running 标志设为 false,并调用工作线程的 interrupt() 方法。
如果在线程处于 sleep() 状态时调用此方法,sleep() 将抛出 InterruptedException(其他阻塞调用也是如此)。
这会使线程跳出阻塞状态回到循环中,并由于 running 已为 false 而正常退出。
4. 结论
在本快速教程中,我们探讨了如何使用原子变量(AtomicBoolean),并可选择性地结合调用 interrupt() 方法,来干净地关闭一个线程。这种方式显然优于调用已弃用的 stop() 方法,后者可能导致永久死锁和内存损坏等问题。