stop Spring 如果在固定时间后挂起,则计划执行

stop Spring Scheduled execution if it hangs after some fixed time

我已经使用 Spring Framework 的 Scheduled 使用 cron 每 5 分钟将我的工作安排到 运行。但有时我的工作会无限等待外部资源,我不能在那里设置超时。我无法使用 fixedDelay,因为之前的过程有时会进入无限等待模式,我必须每 5 分钟刷新一次数据。

所以我在 Spring 框架的 Scheduled 中寻找任何选项来停止 process/thread 在 fixed-time 之后它 运行 成功与否。

我发现下面的设置将 ThreadPoolExecutor 初始化为 keepAliveTime 120 秒,我将其放入 @Configuration class。谁能告诉我这会像我预期的那样工作吗?

@Bean(destroyMethod="shutdown")
public Executor taskExecutor() {
    int coreThreads = 8;
    int maxThreads = 20;
    final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
            coreThreads, maxThreads, 120L, 
            TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()
    );
    threadPoolExecutor.allowCoreThreadTimeOut(true);

    return threadPoolExecutor;
}

我不确定这是否会按预期工作。事实上,keepAlive 是用于空闲线程的,我不知道您等待资源的线程是否处于空闲状态。此外,它仅在线程数大于核心数时才会发生,因此除非您监视线程池,否则您无法真正知道它何时发生。

keepAliveTime - when the number of threads is greater than the core, this is the maximum time that excess idle threads will wait for new tasks before terminating.

您可以做的是:

public class MyTask {

    private final long timeout;

    public MyTask(long timeout) {
        this.timeout = timeout;
    }

    @Scheduled(cron = "")
    public void cronTask() {
        Future<Object> result = doSomething();
        result.get(timeout, TimeUnit.MILLISECONDS);
    }

    @Async
    Future<Object> doSomething() {
        //what i should do
        //get ressources etc...
    }
}

别忘了加上@EnableAsync

也可以在没有 @Async 的情况下通过实现 Callable 来做同样的事情。

编辑:请记住,它会一直等到超时,但线程 运行 任务不会被中断。当 TimeoutException 发生时,您将需要调用 Future.cancel。并在任务中检查 isInterrupted() 以停止处理。如果您正在调用 api,请确保已检查 isInterrupted()。

keepAliveTime只是为了清除暂时不需要的工作线程——它对提交给执行者的任务的执行时间没有任何影响。

如果有任何需要时间的中断,您可以启动一个新线程并在超时时加入它,如果它没有及时完成则中断它。

public class SomeService {

    @Scheduled(fixedRate = 5 * 60 * 1000)
    public void doSomething() throws InterruptedException {
        Thread taskThread = new TaskThread();
        taskThread.start();
        taskThread.join(120 * 000);
        if(taskThread.isAlive()) {
            // We timed out
            taskThread.interrupt();
        }
    }

    private class TaskThread extends Thread {

        public void run() {
            // Do the actual work here
        }
    }
}

allowCoreThreadTimeOuttimeout 设置无济于事,因为它只是允许工作线程在一段时间不工作后结束(参见 javadocs )

你说你的工作无限等待外部资源。我确定这是因为您(或您使用的某些第三方库)默认使用超时无限的套接字。 还要记住 jvm 在套接字上阻塞时忽略 Thread.interrupt() 的内容。connect/read.

因此找出您的任务中使用的 套接字库(以及它的具体使用方式)并更改它的默认超时设置。

例如:RestTemplate 在 Spring 中广泛使用(在 rest 客户端中,在 spring 社交中,在 spring 安全 OAuth 中等等)。还有 ClientHttpRequestFactory implementation to create RestTemplate instances. By default, spring use SimpleClientHttpRequestFactory 使用 JDK 套接字。默认情况下,它的所有超时都是无限的。

所以找出你冻结的确切位置,阅读它的文档并正确配置它。

P.S。如果你没有足够的时间 "feeling lucky" 尝试 运行 你的应用设置 jvm 属性 sun.net.client.defaultConnectTimeoutsun.net.client.defaultReadTimeout 到一些合理的值(有关详细信息,请参阅 docs