你好,我是看山。
在Java中,终止一个线程(Kill a Thread)还是有一定技巧的,本文提供两种方式。
一、使用标志位
我们先创建一个线程类,run方法循环执行一些逻辑,永远不会终止,且不会自行结束。为了服务的稳定,我们需要一种方法停止线程。
本节给出标志位法,简单说就是,有一个原子标志位,可以用来标记当前循环是否执行,如果不可执行,则跳出循环,当前线程即结束。
public class ControlSubThread extends Thread {
private final AtomicBoolean running = new AtomicBoolean(false);
private final AtomicBoolean stopped = new AtomicBoolean(true);
private final int interval;
private final AtomicInteger count = new AtomicInteger(0);
public ControlSubThread(int sleepInterval) {
interval = sleepInterval;
}
@Override
public void start() {
Thread worker = new Thread(this);
worker.start();
}
public void shutdown() {
running.set(false);
System.out.println("线程正在关闭,当前count为:" + count.get());
}
@Override
public void run() {
running.set(true);
stopped.set(false);
while (running.get()) {
try {
System.out.println("线程正在运行(" + System.currentTimeMillis() / 1000 + "): " + count.incrementAndGet());
Thread.sleep(interval);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.out.println("线程被中断,操作未能完成");
}
// 在此处执行一些操作
}
stopped.set(true);
}
}
我们在while循环使用了一个AtomicBoolean,通过控制这个标志位的true或false来启动或终止循环。循环终止了,线程自然也就结束了。
而且需要注意,考虑到JMM,标志位一定要被volatile定义的,为了简答,我们这里选择了AtomicBoolean作为标志位。
我们看下应用:
final ControlSubThread thread = new ControlSubThread(1000);
thread.start();
System.out.println("主线程等待");
TimeUnit.SECONDS.sleep(10);
thread.shutdown();
thread.join();
System.out.println("主线程终止");
运行结果为:
主线程等待 线程正在运行(1733318881): 1 线程正在运行(1733318882): 2 线程正在运行(1733318883): 3 线程正在运行(1733318884): 4 线程正在运行(1733318885): 5 线程正在运行(1733318886): 6 线程正在运行(1733318887): 7 线程正在运行(1733318888): 8 线程正在运行(1733318889): 9 线程正在运行(1733318890): 10 线程正在关闭,当前count为:10 主线程终止
二、中断线程
上面的实现方式会存在一种问题,如果sleep()方法时间过长,或者运行过程出现死锁,永远不会执行到下一次判断,那将长时间阻塞后者无法清除线程。
这个时候我们可以使用interrupt()方法,我们对上面的示例稍加改造:
public class InterruptSubThread extends Thread {
private final AtomicBoolean running = new AtomicBoolean(false);
private final AtomicBoolean stopped = new AtomicBoolean(true);
private final int interval;
private final Thread worker;
private final AtomicInteger count = new AtomicInteger(0);
public InterruptSubThread(int sleepInterval) {
interval = sleepInterval;
worker = new Thread(this);
}
@Override
public void start() {
worker.start();
}
public void shutdown() {
running.set(false);
}
@Override
public void interrupt() {
running.set(false);
worker.interrupt();
}
@Override
public void run() {
running.set(true);
stopped.set(false);
while (running.get()) {
try {
System.out.println("线程正在运行(" + System.currentTimeMillis() / 1000 + "): " + count.incrementAndGet());
Thread.sleep(interval);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.out.println("线程被中断,操作未能完成");
}
// 在此处执行一些操作
}
stopped.set(true);
}
}
在上面示例中,我们添加了一个interrupt()方法,该方法将我们的running标志设置为false,并调用工作线程的interrupt()方法。
Thread.interrupt()在使用上时有一些限制的:
- 权限校验:如果当前线程不是要中断的线程,Thread.interrupt() 方法会调用checkAccess() 方法进行权限检查。如果没有足够的权限,会抛出SecurityException;
- 无法强制终止线程:Thread.interrupt()方法不能强制终止线程。它只是设置中断状态,线程如何响应中断完全取决于线程自身的实现。如果线程忽略了中断状态,线程将继续运行。
- I/O 操作的中断:对于阻塞在 I/O 操作上的线程,中断操作会关闭 I/O 通道并抛出ClosedByInterruptException。这可能导致资源泄漏或未完成的操作,因此需要谨慎处理。
如果在调用此方法时线程正在睡眠,sleep()方法将抛出InterruptedException异常退出,其他任何阻塞调用也会如此。这样能够快速打断休眠和暂停。
在这个快速教程中,我们研究了如何使用原子变量,并可选择结合调用interrupt()方法,来干净地关闭一个线程。这绝对比调用已弃用的stop()方法要好,因为调用stop()方法可能会导致永远锁定和内存损坏的风险。
引申一下
虽然本文主题是终止线程,但其实可以推广到所有while(true)的场景中。在可能出现死循环或者有条件的大循环中,我们都应该适当的增加标记位,可以快速终止循环。
比如,批量刷数据场景,通常是在循环中分页查询数据,然后批量处理这部分数据,处理后进入下一个批次。但是如果分页较深,或者额运行时间较长,亦或是有bug,需要快速停止,都可以通过状态位实现。