如何正确停止一个线程?Java 线程的中断机制详解

目录

1、Java 线程中断机制原理

2、线程 sleep 期间能否感受到中断标志?

3、线程中断的使用场景


        当点击某个杀毒软件的取消按钮来停止查杀病毒时,或者在控制台敲入quit 命令以结束某个后台服务时……都需要通过一个线程去取消另一个线程正在执行的任务。Java 没有提供一种安全直接的方法来停止某个线程,但是Java提供了中断机制// 中断线程并不是停止线程

弃用 stop() :

        stop() 方法已经被 JDK 废弃,原因就是 stop() 方法太过于暴力,强行把执行到一半的线程终止,存在线程安全问题。

1、Java 线程中断机制原理

        Java中断机制是一种协作机制,也就是说通过中断并不能直接终止另一个线程,而需要被中断的线程自己处理(由程序员决定线程如何响应中断)被中断的线程拥有完全的自主权,它既可以选择立即停止,也可以选择一段时间后停止,也可以选择压根不停止。这好比是家里的父母叮嘱在外的子女要注意身体,但子女是否注意身体,怎么注意身体则完全取决于自己。

        中断机制是使用一个称为中断状态的内部标志来实现的。调用 Thread.interrupt() 设置此标志,当线程通过调用静态方法 Thread.interrupted() 来检查中断时,中断状态将被清除。非静态的 isInterrupted() 方法被一个线程用来查询另一个线程的中断状态,它不会改变中断标志的状态。// Java 线程中断机制的核心方法

方法方法描述
public static boolean interrupted()判断,静态方法,清除中断标志,重置为 fasle
public boolean isInterrupted()判断,非静态方法,不改变中断标志,通过线程实例调用
public void interrupt()设置,用来设置中断标志

        其中, interrupt() 方法是唯一能将中断状态设置为 true 的方法。静态方法 interrupted() 会将当前线程的中断状态清除,但这个方法的命名极不直观,很容易造成误解,需要特别注意。

public class ThreadInterruptTest {
    // 计数器
    static int i = 0;
    public static void main(String[] args) {
        System.out.println("begin");
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    i++;
                    System.out.println(i);
                    // Thread.interrupted() 清除中断标志位
                    // Thread.currentThread().isInterrupted() 不会清除中断标志位
                    if (Thread.interrupted()) {
                        System.out.println("=========");
                    }
                    if (i == 5) {
                        break;
                    }
                }
            }
        });
        t1.start();
        // 不会停止线程t1,只会设置一个中断标志位 flag=true
        t1.interrupt();
    }
}

        interrupt() 方法不会停止线程,只会给线程设置一个中断标志位 flag = true。

        利用中断机制优雅的停止线程:

public class StopThread implements Runnable {
    @Override
    public void run() {
        int count = 0;
        // 判断中断标记
        while (!Thread.currentThread().isInterrupted() && count < 1000) {
            System.out.println("count = " + count++);
        }
        System.out.println("线程停止: stop thread");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new StopThread());
        thread.start();
        Thread.sleep(1);
        thread.interrupt(); // 设置中断标记为true
    }
}

        注意:使用中断机制时一定要注意是否存在中断标志位被清除的情况

2、线程 sleep 期间能否感受到中断标志?

        修改上面的代码,线程执行任务期间有休眠需求:

public class StopThread implements Runnable {
    @Override
    public void run() {
        int count = 0;
        // 判断中断标记
        while (!Thread.currentThread().isInterrupted() && count < 1000) {
            System.out.println("count = " + count++);
            // 增加休眠
            try {
                Thread.sleep(1); // 抛出异常后,同时清除中断信号
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("线程停止: stop thread");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new StopThread());
        thread.start();
        Thread.sleep(5);
        thread.interrupt(); // 主线程设置中断标记为true
    }
}

        处于休眠中的线程被中断,线程是可以感受到中断信号的,并且会抛出一个 InterruptedException 异常,同时清除中断信号,将中断标记位设置成 false。这样就会导致 while 条件 Thread.currentThread().isInterrupted() 为 false,程序会在不满足 count < 1000 这个条件时退出。如果不在 catch 中重新手动添加中断信号,不做任何处理,就会屏蔽中断请求,有可能导致线程无法正确停止。// 异常清除了中断标志

        所以我们需要在 catch 中重新手动添加中断信号,避免线程无法正确停止。

// 增加休眠
try {
    Thread.sleep(1); // 抛出异常后,同时清除中断信号
} catch (InterruptedException e) {
    e.printStackTrace();
    //重新设置线程中断状态为true
    Thread.currentThread().interrupt();
}

3、线程中断的使用场景

        通常,中断的使用场景有以下几个:

  • 点击某个桌面应用中的取消按钮时;
  • 某个操作超过了一定的执行时间限制需要中止时;
  • 多个线程做相同的事情,只要一个线程成功其它线程都可以取消时;
  • 一组线程中的一个或多个出现错误导致整组都无法继续时;
  • 当一个应用或服务需要停止时。

        下面来看一个具体的例子。这里新建了一个磁盘文件扫描的任务,扫描某个目录下的所有文件并将文件路径打印到控制台,扫描的过程可能会很长。若需要中止该任务,只需在控制台键入quit并回车即可。// 因为控制台不好输入,代码中设置为 3s 后自动中断

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;

public class FileScanner {
    private static void listFile(File f) throws InterruptedException {
        if (f == null) {
            throw new IllegalArgumentException();
        }
        if (f.isFile()) {
            System.out.println(f);
            return;
        }
        File[] allFiles = f.listFiles();
        if (Thread.interrupted()) {
            throw new InterruptedException("文件扫描任务被中断");
        }
        if(allFiles != null && allFiles.length > 0){
            for (File file : allFiles) {
                //还可以将中断检测放到这里
                listFile(file);
            }
        }
    }

    // 控制台的输入
    public static String readFromConsole() {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        try {
            return reader.readLine();
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }
    }

    // 3秒后返回"quit",打上中断标记
    public static String gainFlagFromTask() {
        try {
            Thread.currentThread().sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "quit";
    }

    public static void main(String[] args) throws Exception {
        final Thread fileIteratorThread = new Thread() {
            public void run() {
                try {
                    listFile(new File("C:\\"));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        new Thread() {
            public void run() {
                while (true) {
                    if ("quit".equalsIgnoreCase(gainFlagFromTask())) {
                        if (fileIteratorThread.isAlive()) {
                            fileIteratorThread.interrupt();
                            return;
                        }
                    } else {
                        System.out.println("输入quit退出文件扫描");
                    }
                }
            }
        }.start();
        fileIteratorThread.start();
    }
}

        在扫描文件的过程中,对于中断的检测这里采用的策略是,如果碰到的是文件就不检测中断,是目录才检测中断,因为文件可能是非常多的,每次遇到文件都检测一次会降低程序执行效率。

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

swadian2008

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值