一文学会终止线程的两种方式

开发 前端
虽然本文主题是终止线程,但其实可以推广到所有while(true)的场景中。在可能出现死循环或者有条件的大循环中,我们都应该适当的增加标记位,可以快速终止循环。

你好,我是看山。

在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,需要快速停止,都可以通过状态位实现。

责任编辑:武晓燕 来源: 看山的小屋
相关推荐

2020-08-03 08:01:50

爬虫技巧

2020-08-31 06:54:37

注解脱敏ELK

2020-04-20 10:47:57

Redis数据开发

2021-06-28 14:13:34

OOM内存事故

2021-04-30 07:33:35

效率提升技巧

2021-04-28 07:22:13

HiveJson数组

2021-06-26 09:26:01

Jupyter主题目录

2021-08-04 07:47:18

IDEJTAGSWD

2020-04-19 21:41:13

Python数据可视化

2021-03-29 08:24:18

KubeadmKubernetes1运维

2010-07-14 10:30:26

Perl多线程

2021-11-01 13:55:38

架构

2010-02-02 14:32:32

Python线程编程

2023-11-01 10:49:50

Python面向对象

2019-03-21 09:45:11

TypeScript编程语言Javascript

2025-01-16 08:38:34

2021-04-07 08:13:28

LirbeNMS开源SNMP

2023-09-26 12:22:37

队列Python

2019-11-12 09:15:18

MySQL复制拓扑Orchestrato

2023-07-31 08:18:50

Docker参数容器
点赞
收藏

51CTO技术栈公众号