Java:杀死由 ExecutorService 作为 Runnable 启动的线程

问题描述 投票:0回答:3

我有一个系统,当它收到来自网络服务的调用时,它会启动工作程序。 Worker 由 ExecutorService 启动,启动的类实现 Runnable。但是,如果工作线程超时,我无法真正杀死该工作线程,这会导致我的系统出现资源问题。

public class MyClass implements Runnable {

    public void internalCall() {
        logger.info("B-1");
        //Some business code which may take too long
        // <...>
        logger.info("B-2");
    }

    public void launch() {
        // Wrapper
        Callable<Object> callable = new Callable<Object>() {
            @Override
            public Object call() throws Exception {
                internalCall();
                return null;
            }
        };

        // Submit
        ExecutorService executor = Executors.newSingleThreadExecutor();
        Future<Object> future = executor.submit(callable);

        try {
            // Wait
            future.get(1, TimeUnit.SECONDS);
        }
        catch (TimeoutException e) {
            logger.warn("Timeout");
        }
        finally {
            logger.info("A-1");
            executor.shutdownNow();
            future.cancel(true);
            logger.info("A-2");
        }
    }
}

如果工作线程超时,我预计会出现以下日志消息:

INFO | B-1
WARN | Timeout
INFO | A-1
INFO | A-2

随后服务保持空闲状态,直到另一个工作程序请求到来。但是,尽管分别在 ExecutorService 和 Future 上调用 shutdownNow() 和 cancel(),工作程序仍继续:

INFO | B-1
WARN | Timeout
INFO | A-1
INFO | A-2
INFO | B-2

我环顾四周,发现还有许多其他关于终止线程的类似问题,普遍的共识是你不应该这样做。然而,这是一个可以扩展的类,目的是覆盖internalCall() - 这意味着我不能依赖internalCall来监管自身并检查Thread.isInterrupted()或类似的东西。

我想通过攻击furure或executor对象来强制杀死launch()方法中的东西。

java multithreading runnable executorservice
3个回答
3
投票

注意

如果线程没有响应Thread.interrupt怎么办?

在某些情况下,您可以使用特定于应用程序的技巧。例如, 如果线程正在等待已知的套接字,您可以关闭该套接字 导致线程立即返回。不幸的是,确实有 不是任何通用的技术。 需要注意的是,在 等待线程不响应的所有情况 Thread.interrupt,它也不会响应Thread.stop。 这样 案例包括故意拒绝服务攻击和 I/O 操作 thread.stop 和 thread.interrupt 无法正常工作。 - Java 线程原语弃用

那么我们学到了什么......在 Java 中,不要运行您确实无法信任的第三方代码,其执行方式与您的主应用程序相同。另外,即使 Thread.stop 确实起作用,你仍然有一大堆其他事情比不检查中断状态的线程要糟糕得多(即代码调用

System.exit(0)
)。

我建议您对不能信任的第三方代码执行以下操作之一:

  • 将第三方代码作为由您控制执行的 Java 运行的评估语言来运行。一些例子是:像 Drools 这样的规则语言或像 JMustache 这样的无逻辑模板语言。

  • 在单独的执行中运行第三方代码,并使用您的操作系统杀死进程和IPC(例如用于通信的套接字)。


0
投票

首先,

future.cancel(true)
不会杀死正在运行的线程。它只是试图以“礼貌的方式”停止其执行。

它的作用是发送中断信号,或多或少,就像你在代码中的某个地方调用

yourthread.interrupt()
一样。

仅当 run() 方法内的代码检查中断时,它才会停止处理。因此,在您的

internalCall()
内部,您需要时不时地检查线程是否没有被调用
Thread.interrupted()
中断并停止执行。中断还会通过抛出 InterruptedExcepiton 来停止 sleep()、wait()、IO 操作(这就是这些方法抛出此类异常的原因)。

ExecutorService
使用相同的机制,因此它将尝试中断正在运行的线程,但同样,如果您的代码不检查此类中断,则线程将继续运行。

由于各种原因,线程不应该被简单地杀死(检查java文档中的thread.stop()方法以及为什么它被弃用),通知线程可能应该停止工作的“通用”方法是中断信号。


0
投票

我使用 SingleThreadExecutor 和 Future 进行了相同的设置。但由于我无法控制我执行的代码,因此我无法实现对

Thread.interrupted()
的检查。
(我可以通过字节码转换添加它们,但需要将它们放置在每个语句之前,以防止发生无限循环之类的事情)。

我的解决方案是使用普通的

Thread
对象和
TimerTask

var executionThread = new ExecutionThread(/* params... */);
var timer = new Timer();
var timeOutTask = new TimeOutTask(executionThread, timer);
timer.schedule(timeOutTask, MAX_EXECUTION_TIME_SECONDS * 1_000);
executionThread.start();

try {
    // wait for the execution to finish:
    executionThread.join();
} catch (InterruptedException e) {
    // I throw an exception here for example, but this case will usually not happen
}

if (timeOutTask.isThreadTimedOut()) {
    // custom exception I publish to the client, you can handle it different though
    throw new ExecutionTimedOut(MAX_TEST_EXECUTION_TIME_SECONDS);
}

// the result that you would usually receive from the future.
return executionThread.getResult();

自定义线程类可以如下所示:
(我用的是lombok,你也可以手动写出构造函数和getter)

@RequiredArgsConstructor
class ExecutionThread extends Thread {
    @Getter private Result result;

    private final Things i_need_for_execution;

    @Override
    public void run() {
        result = // execute the task here ...
    }
}

计时器可以这样复制:

@RequiredArgsConstructor
class TimeOutTask extends TimerTask {
    private final Thread thread;
    private final Timer timer;
    @Getter private boolean threadTimedOut = false;

    @Override
    public void run() {
        if (thread != null && thread.isAlive()) {
            //noinspection deprecation
            thread.stop();
            timer.cancel();
            threadTimedOut = true;
        }
    }
}

我希望有一个不涉及已弃用的

Thread::stop
的解决方案,但这是我找到的唯一方法。

© www.soinside.com 2019 - 2024. All rights reserved.