如何在Java中停止正在运行的线程(异步进程)

iyfjxgzm  于 2023-04-28  发布在  Java
关注(0)|答案(3)|浏览(291)

此问题已在此处有答案

How to timeout a thread(17回答)
java killing a thread after timeout value [duplicate](5个答案)
stop a thread in java after a given time - doesn't work(5个答案)
How to stop execution after a certain time in Java?(4个答案)
昨天关门了。
我有一个Job类,它在执行器服务中触发3个不同的可运行任务。线程可能需要更长的时间(有时甚至需要1小时才能完成任务)。如果它们花费的时间比预期的要长,我想停止线程。有没有一种有效的方法来停止异步进程?
作业在某个时间点被触发(使用调度程序cron exp),如果作业占用更多时间,则需要停止作业。

public class MyJob {
   . . . . . . . . . .
   . . . . . . . . . .
   public void process(..) {
     . . . . . . . . . .
     . . . . . . . . . .
     Runnable r1 = () -> { task1() };
     Runnable r2 = () -> { task2() };
     Runnable r3 = () -> { task3() };

     List.add(r1, r2, r3);
   }

   private void task1() {
     . . . . . . . . . .
   }

   private void task2() {
     . . . . . . . . . .
   }

   private void task3() {
     . . . . . . . . . .
   }

   private void runAsync(List<Runnable> tasks) {
     . . . . . . . . . .
     myExecutorService.submitAll(tasks);
     . . . . . . . . . .
   }
}

我读到过中断,但不确定它是否符合我的要求。我想定义一个API端点,通过它我可以停止异步进程。

w1jd8yoj

w1jd8yoj1#

在Java中,基本上有4个原语来支持线程间通信:
1.不稳定的变量。
1.中断系统。
1.同步(方法或块)。

  1. wait()+ notify()系统。
    为了完整起见,还有Thread.stop()Thread.suspend()方法-但根据文档,它们本质上是不安全的,不应该使用。现在已弃用,最终将被删除。参见Why are Thread.stop, Thread.suspend and Thread.resume Deprecated?
    此外,你应该查看java.util.concurrent包--然而我的理解是,并发包是建立在上面列出的4个原语之上的,因此它不提供任何额外的功能(你不能使用这些原语自己实现)。
    上面列出的前两个原语可以被视为暂停/中止运行线程的机制。最后两个与同步/正确性和性能优化(没有空闲线程燃烧CPU)更相关。
    volatile变量最简单的情况是一个布尔标志shouldTerminate,它被初始化为false。那么你在工作线程中的主处理循环通常会有如下内容:
while(!shouldTerminate) {
  // Do something.
}

因此,如果你想用信号通知一个工作线程终止,你可以简单地设置shouldTerminate = true;,工作线程就会跳出它的循环。您有多少个标志以及哪些worker线程使用哪些标志决定了您对worker何时终止的控制级别。
(仅)使用volatile变量的核心问题是线程可能会以多种方式被阻塞:

  • 等待监视器(同步块)-可能在wait()期间
  • 执行线程。sleep()
  • 正在执行IO。

如果你只使用volatile变量,线程不会中止,直到任何情况下阻止它的执行被清除-一旦块被清除,线程将能够检查变量,如果需要关闭。
这就是notify()和interrupt()的作用:

  • notify()允许您指示另一个线程中断wait()。
  • interrupt()通常会将另一个线程从sleep()中中断出来和/或如果它在IO上被阻塞。

注意:notify()唤醒一个(单个)随机线程,因此,如果你有多个线程在一个监视器上等待,通常使用notifyAll()是一个好的做法。
有没有一种有效的方法来停止异步进程?
在调度作业时,您可以在返回的Future上执行cancel(),但如果作业已经在运行,则将其实现为interrupt()
或者,您可以定义一个变量来向作业发出它应该终止的信号--这通常是一个更干净的解决方案--但它有上面概述的缺点。

编辑-原子类

正如@Basil Bourque -在评论中指出的那样,Atomic...类是一个灰色地带。我不认为它们本身是原语,但它们确实利用了我在上面的列表中没有提到的原语(来自VarHandle类的CAS操作)。
上面的原语列表通常是您可以选择直接使用的,我建议使用Atomic...类,而不是直接调用CAS操作。
TBF -自从添加了java.util.concurrent之后,我就没有使用过interrupt()或等待/通知太多。我通常会发现其中一个并发类满足我的需求,但我仍然使用volatile布尔值来终止后台线程的执行--这是因为我不知道第三方库(通常用于工作线程)如何科普中断。因此,我宁愿接受会有延迟,而任何IO在终止发生之前完成。

nfs0ujit

nfs0ujit2#

这取决于你是否想控制止损。
当你将submit()一个Runnable放入执行器服务时,你会得到一个Future,它声明了一个方法cancel(booleam mayInterruptIfRunning)
您可以:
要么你公开这个期货列表,让调用者决定该怎么做。在这种情况下,你的void process()变成了List<Future<?>> process(),所以调用这个方法的人,拥有正在运行的任务列表,并可以决定停止其中的一些任务。
或者,您在内部Map这些任务并公开一个方法来停止任务,例如,通过名称。就像这样:

Map<String, Future<?>> tasksMap = new HashMap<>();
tasksMap.add("task1", executorService.submit(runnable1));
tasksMap.add("task2", executorService.submit(runnable2));
//etc.

他们可以在外面给你打电话

public void stopTask(String taskName) {
    tasksMap.get(taskName).cancel(true/false);
    //I let you design the proper checks to make sure an entry of the map is present and that the thread is still running before cancelling it
}
6qftjkof

6qftjkof3#

我个人会将任务的开始日期保存到一个全局变量中,并创建另一个调度程序,它会在指定的时间间隔(取决于您的需要)进行检查,如果之前存储的现在开始日期高于1小时。如果是,我会调用myExecutorService。shutdownNow()方法。

相关问题