首页 > 解决方案 > 为什么不调用线程中断后的方法?

问题描述

我想优雅地关闭一个线程。但是,一旦启动关闭,线程应该在结束正常操作后执行一些关闭操作。

两个线程都使用睡眠和/或等待并处理 InterruptedException,它们还可以循环处理任务,只需几毫秒。所以我希望 while 循环结束,因为 Thread.currentThread().isInterrupted() 变为“真”。

问题是我的代码有时会得到日志“SHUTDOWN”,有时不会。我也只是有时会得到“中断”,这是我当然理解的。对于另一个类似的线程,我永远不会得到“关闭”。

ExecutorService executor = Executors.newFixedThreadPool(2);

executor.execute(new Test());

Thread.sleep(10000);

executor.shutdown();
        
try {
    if(this.executor.awaitTermination(60, TimeUnit.SECONDS)) {
        this.loggerFactory.getLogger(this.getClass()).info("CLOSED (GRACEFULLY)!");
    }  else {
        this.executor.shutdownNow();
        
        this.loggerFactory.getLogger(this.getClass()).info("CLOSED (IMMEDIATELY)!");
    }
} catch(InterruptedException e) {
    this.executor.shutdownNow();
            
    this.loggerFactory.getLogger(this.getClass()).info("CLOSED (IMMEDIATELY)!");
}
class Test implements Runnable {

    private volatile boolean isRunning = true;

    @Override
    public void run() {
        try {
            while(!Thread.currentThread().isInterrupted()) {
                while(!this.isRunning) {
                    synchronized(this) {
                        this.wait();
                    }
                }
                
                // DO SOMETHING LASTING A FEW MILLISECONDS
                    
                Thread.sleep(500);
            }
        } catch(InterruptedException e) {
            this.loggerFactory.getLogger(this.getClass()).info("INTERRUPTED!");
        }
        
        this.loggerFactory.getLogger(this.getClass()).info("SHUTDOWN!");

        // DO SOME SHUTDOWN OPERATION
    }

}

标签: javamultithreading

解决方案


编辑:

在 OP 发表一些评论之后,似乎可以使用一个完全不同且更优越的解决方案:

使用钩子!

Java 有一个系统来“安装”一个关闭钩子。这些在VM关闭时被调用......有时。如果您收到 SIGTERMed ( kill -9) 或者有人绊倒了电源线,或者由于内存使用过多,或者内核转储,或者您的 VM 硬崩溃(例如,本机代码中的核心转储)导致 linux 杀死了您的进程,或者设备丢失权力,他们不会被召唤,当然。

但是,如果进程中的某个人正在运行System.exit(),或者所有非守护线程都已完成,或者有人按下 CTRL+C 或向您的进程发送 SIGKILL ( kill, not kill -9),则它们会首先运行,并且只有当它们全部完成时才会运行 java 进程实际上结束。

这听起来像是一个非常优越的解决方案。您的关闭挂钩应该:

  1. 获取一些私有 AtomicBoolean 的锁定。
  2. 将布尔值设置为 false(布尔值表示:我可以查询这个传感器吗?)
  3. 释放锁。
  4. 重置传感器。
  5. 返回。

读取该传感器的所有正常操作代码都应该:

  1. 获取布尔值的锁。
  2. 如果为假,则抛出或以其他方式中止。
  3. 执行传感器读取操作。
  4. 释放锁。

没有锁住任何东西都不应该接触那个传感器(如果不这样做可能意味着在你已经重置它之后可能会弄乱那个传感器,这会很糟糕)。


原答案:

我想优雅地关闭一个线程。

为什么?“优雅地”是一个很好听的词,但一旦你深入了解它的含义,它就是令人讨厌的东西。这个词的意思是:“如果有人绊倒电源线或我的应用程序硬崩溃,这将导致我的软件可能持续失败(例如,如果不清理东西就无法启动)”。

一个更好的设计是有一个不需要关闭的线程。只需拔下插头,一切都很好。

例如,旧的文件系统(MS-DOS 和早期的 Windows 时代)需要正常关闭;不这样做会导致持续的问题——系统根本无法启动,你把盒子弄砖了。然后他们有了缓解系统(chkdsk 系统),但现代操作系统要好得多。他们的文件系统处理设置大多不关心被“优雅地”关闭。只要拔掉它们的插头,它们就会没事的,这就是期刊所做的。

So that I expected the while loop to end because Thread.currentThread().isInterrupted() becomes "true".

这不是您应该使用该 API 的方式。

以下是中断 API 的基本要点:

  1. 任何线程都可以在任何其他 ( someThread.interrupt()) 上“提高中断标志”。

  2. 除非方法明确决定查看它,否则升旗除了升旗外不会做任何事情。

  3. 该方法Thread.interrupted()是您应该如何读取标志以便对其采取行动,__而不是Thread.currentThread().isInterrupted()。前者将检查标志并清除它。后者仅检查标志。

  4. 一些 java 方法被指定来响应标志的上升。你认识这些方法是因为它们throws InterruptedException。可能有更多的方法;例如,在大多数操作系统上,中断当前正在等待更多字节从网络流入的线程(它们在read()调用InputStreamfrom时被阻塞socket.getInputStream())将导致读取调用失败(出现 IOException,而不是 InterruptedException,因为read() 不会抛出 InterruptedEx),但这不能保证;在某些操作系统上,它不会,你不能打断它。

  5. 一般的策略是,在处理中断标志的那一刻,降低标志,java 代码就是这样做的:如果方法抛出 InterruptedEx,标志将被清除

  6. Java 没有定义如果被中断应该做什么。线程不会被神奇地中断;例如,当您的 VM 关闭时(有人按 CTRL+C),这不会中断任何线程。Java 只会在所有线程上“拔掉插头”。那是因为这样更好(见上文)。因此,如果一个线程被中断,那是因为你写了thread.interrupt()某个地方,因此,你决定它的含义。也许这意味着“重新读取配置文件并重新启动服务器侦听过程”。也许这意味着“停止计算国际象棋移动并执行迄今为止发现的最佳移动”。也许它的意思是“重新检查一个条件”。也许这意味着“完全结束线程”。由你决定。没有标准。

请注意,指定响应中断标志的各种方法(例如wait():它会抛出 InterruptedException)都共享此属性:如果您在标志上升时调用它们,它们将立即通过抛出返回InterruptedException,它们甚至不会开始等待。

因此,对于您的代码,鉴于您已经 wait(),只需执行此while(true)操作并依赖 InterruptedEx。


推荐阅读