【问题标题】:Best practice to immideately stop Thread立即停止线程的最佳实践
【发布时间】:2021-05-08 05:05:49
【问题描述】:

我有上下文,可以用来产生预测。基于上下文进行预测的算法不是我的问题的一部分。但它很耗时,所以我将它从主线程中移出。在任何时候上下文都可能改变。发生这种情况时,应用程序应使用新输入重新启动预测生成。与旧上下文相关的预测将变为无效,因此应中断其计算。

现在我使用代码:

public class BackgroundTasksManager {

public static final BackgroundTasksManager shared = new BackgroundTasksManager();

private ExecutorService conceptResultsExecutor = null;

private BackgroundTasksManager() { }

public void startConceptResultsCalculation(ConceptResultsCalculationInput input,
                                           Consumer<ConceptResultsCalculationOutput> callback) {
    ThreadGroup callbackGroup = Thread.currentThread().getThreadGroup();
    if (conceptResultsExecutor != null)
        conceptResultsExecutor.shutdownNow();

    conceptResultsExecutor = Executors.newSingleThreadExecutor();
    conceptResultsExecutor.submit(() -> {
        ConceptResultsCalculationOutput output = ConceptResultsCombinator.possbileResults(input);
        Thread callbackThread = new Thread(callbackGroup, () -> callback.accept(output));
        callbackThread.start();
    });
}

所以,当我需要开始预测计算时,我关闭了旧的 Executor 并创建了一个新的。此解决方案有效,但它是否有效,或者隐藏了一些危险的细微差别?

【问题讨论】:

  • java 平台只有一种标准机制来中断线程。这是中断标志。并且线程应该自己检查标志。任何长/阻塞操作都应该检查这个标志。例如,如果你有一个长时间运行的循环,你应该定期检查 interrupted()/isInterrupted() 标志。顺便说一句,要中断像 socket.read() 这样的阻塞 IO 操作,更好的方法是在另一个线程中 close() 以立即从 read() 中退出并抛出 IOException。
  • 2.不需要使用合成 volatile 标志来停止线程计算,但这可能是线程将被用户停止的标志,因此,将标志关闭设置为 true 并在运行的 catch 部分检查它( ) 方法。如果 !close,你有问题,否则这是从 run() 退出的预期异常。 3.不要忘记,通常建议在捕获InterruptedException后恢复中断标志javapractices.com/topic/TopicAction.do?Id=251
  • @AnatolyG 有时会调用 interrupt() 来唤醒正在休眠或等待可中断锁的线程,而不打算停止它。在这种情况下,检查 interrupted()/isInterrupted() 是不够的。中断的另一个问题是它们会在中间中断某些功能,而最好将文件读到最后,然后检查自定义标志。
  • @Andrea 当然,我可以想象这样的独家设计方案:),但在我看来,这似乎不是像 Object wait()/notify() 这样的规范的线程间通信机制,Locks+Conditions,BlockingQueues 等。我想在标准库或任何众所周知的库中看到一个例子。至于“中途中断某个函数”,这是合作机制(ibm.com/developerworks/library/j-jtp05236/index.html),也就是说如果这是你的函数,你可以在恢复中断标志并退出之前做你需要做的事情,例如,你关闭你的文件描述符。
  • 但是如果 GUI 线程只是要求我们的解压缩函数停止,我希望该函数尽快停止并且不要读取和解压缩我的 10gigs 文件的其余部分)如果我们想使用ExecutorService 中的函数,该服务不知道我们自己的自定义标志,而只知道中断。

标签: java multithreading


【解决方案1】:

您可以使用 Thread::stop。该方法已被长期弃用(自 1.2 起),但它仍然存在于 Java 15 中。javadocs 中所述方法的问题在于

这种方法本质上是不安全的。使用 Thread.stop 停止线程 导致它解锁所有已锁定的监视器(作为 未经检查的 ThreadDeath 异常传播的自然结果 在堆栈上)。如果以前受这些保护的任何对象 监视器处于不一致的状态,损坏的对象变为 对其他线程可见,可能导致任意行为。 停止的许多用法应该被简单地修改一些代码的代码所取代 变量来指示目标线程应该停止运行。这 目标线程应该定期检查这个变量,并从 如果变量表明它以有序的方式运行它的方法 是停止运行。如果目标线程等待很长时间(在 条件变量,例如),应使用中断方法 中断等待。

问题不仅与锁定有关,还与任何在中间突然中断的阐述有关,使您的数据不一致。 但是你可以使用 try finally 来做同样的清理

void run() {
    try {
       runInner();
    }
    finally {
        //do same checks
        //this is executed also if stop() is called where execution was inside runInner()
    }

插入一些检查的方法

volatile boolean myThreadStopping=false;

void run() {
    // some code
    if (myThreadStopping) return;
    // some code
    if (myThreadStopping) return;
    for (...) {
       // some code
       if (myThreadStopping) return;
    }
}

具有控制退出点的优势,它允许您在从任务返回时保持数据和锁定一致。如果您的代码中有任何睡眠,则可以在 myThreadStopping=true 之后使用 Thread::interrupt。

主动检查的最大限制是阻塞 IO 方法。例如,如果在您的代码中,您从 Socket 的 InputStream 读取数据,Thread::interrupt 将无法帮助您继续执行 myThreadStopping 变量的下一次检查,并且 Thread 将在 read() 处保持等待。

您可以将这两种方法结合起来。

【讨论】:

    【解决方案2】:

    要停止线程-您可以通过直接调用Thread::interrupt 来interrupt 它,或者在您的情况下调用shutDownNow,这将隐式调用它。或者你可以有一个volatile boolean stop 标志,其他一些线程需要设置并且你的线程需要检查。

    如果您的线程不响应中断,那么 shutDownNow 将不会做任何事情,除了阻塞 未来 将在其上安排的任务。

    【讨论】:

    猜你喜欢
    • 2010-10-14
    • 1970-01-01
    • 1970-01-01
    • 2014-06-04
    • 1970-01-01
    • 1970-01-01
    • 2014-06-14
    • 2015-12-04
    • 2010-09-05
    相关资源
    最近更新 更多