您好我有关于分配给线程池java的任务的超时问题。
详细说明:
Executors.newFixedThreadPool(40)
创建了一个固定的线程池。每当有人调用此API时,一组10个任务就是此线程池的调度。这些任务在内部对Mysql执行一组查询。 问题:
有没有一种优雅的方法来解决这个问题?
@Component
public class MyHandler {
@PostConstruct
public void init() {
/*
* Naming thread pool to identify threads in the thread dump
* */
ThreadFactory threadFactory = new ThreadFactoryBuilder()
.setNameFormat("my-thread-%d").build();
executorService = Executors.newFixedThreadPool("40", threadFactory);
}
@PreDestroy
public void destroy() {
executorService.shutdown();
}
public void update() {
List<Future<Boolean>> results = new ArrayList<>();
results.add(executorService.submit(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
executeQuery();
return true;
}
}));
/*
* 9 more such tasks
*/
for (Future<Boolean> result : results) {
try {
result.get();
} catch (InterruptedException | ExecutionException e) {
LOGGER.error("Failed with unknown error", e);
}
}
}
}
executeQuery()
超时计划为60秒。
答案 0 :(得分:2)
您可以使用ExecutorService.invokeAll()
以超时运行任务集合。方法完成(完成工作或超时)后,您必须检查所有期货以查看它们是否被取消(由于超时)或已完成。如果他们完成了,你必须检查他们是否完成了因为工作已经完成,而不是因为例外(当你致电Future.get
时)。
代码可能如下所示:
final ExecutorService service = Executors.newCachedThreadPool();
final List<Future<Double>> futures = service.invokeAll(tasks, 2, TimeUnit.SECONDS);
final List<CallableTask> tasks = Arrays.asList(new CallableTask(1, TimeUnit.SECONDS),
new CallableTask(1, TimeUnit.HOURS), new CallableTask(100, TimeUnit.MILLISECONDS),
new CallableTask(50, TimeUnit.SECONDS));
for (Future<Double> result : futures) {
if (!result.isCancelled()) {
try {
System.out.println("Result: " + result.get());
} catch (ExecutionException e) {
// Task wasn't completed because of exception, may be required to handle this case
}
}
}
在我的例子中,CallableTask是一个Callable实现,它用于使代码更简单,因为提交的所有任务都是相同的。您可以使用相同的方法来简化代码。
我添加了CallableTask的样子:
public class CallableTask implements Callable<Double> {
private static AtomicInteger count = new AtomicInteger(0);
private final int timeout;
private final TimeUnit timeUnit;
private final int taskNumber = count.incrementAndGet();
public CallableTask(int timeout, TimeUnit timeUnit) {
this.timeout = timeout;
this.timeUnit = timeUnit;
}
@Override
public Double call() {
System.out.println("Starting task " + taskNumber);
try {
timeUnit.sleep(timeout);
} catch (InterruptedException e) {
System.out.println("Task interrupted: " + taskNumber);
Thread.currentThread().interrupt();
return null;
}
System.out.println("Ending task " + taskNumber);
return Math.random();
}
}
答案 1 :(得分:1)
您可以使用ExecutorSerive.invokeAll(List<Callable<T>> tasks, long timeout, TimeUnit timeUnit)
(https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html#invokeAll-java.util.Collection-)。看一下下面的代码示例:
package com.github.wololock;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
final class ExecutorsServiceInvokeAnyExample {
public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
final ExecutorService executor = Executors.newFixedThreadPool(5);
final List<Callable<String>> tasks = Arrays.asList(
() -> {
debug("This task runs for 1 second");
Thread.sleep(1000);
debug("Task completed!");
return "1";
},
() -> {
debug("This task runs for 2 seconds");
Thread.sleep(2000);
debug("Task completed!");
return "2";
},
() -> {
debug("This task runs for 3 seconds");
Thread.sleep(2999);
debug("Task completed!");
return "3";
},
() -> {
debug("This task runs for 4 seconds");
Thread.sleep(4000);
debug("Task completed!");
return "4";
},
() -> {
debug("This task runs for 5 seconds");
Thread.sleep(5000);
debug("Task completed!");
return "5";
}
);
try {
final List<Future<String>> result = executor.invokeAll(tasks, 3, TimeUnit.SECONDS);
if (result.stream().anyMatch(Future::isCancelled)) {
throw new RuntimeException("All tasks were not completed...");
}
} finally {
executor.shutdown();
}
}
private static void debug(String msg) {
System.out.println("[" + Thread.currentThread().getName() + "] " + msg);
}
}
我们正在触发invokeAll
5个任务,其中最快的一个需要1秒才能完成,而最慢的任务需要5秒才能完成。调用超时设置为3秒,此时只完成3个任务。在这个例子中,如果不是所有任务都完成,我会抛出一个RuntimeException
- 这取决于你的业务案例,如果发生这种情况,你会做什么。以下是运行此示例的示例输出:
[pool-1-thread-2] This task runs for 2 seconds
[pool-1-thread-1] This task runs for 1 second
[pool-1-thread-4] This task runs for 4 seconds
[pool-1-thread-3] This task runs for 3 seconds
[pool-1-thread-5] This task runs for 5 seconds
[pool-1-thread-1] Task completed!
[pool-1-thread-2] Task completed!
[pool-1-thread-3] Task completed!
Exception in thread "main" java.lang.RuntimeException: All tasks were not completed...
如果我给6秒超时,那么一切都正确完成,并且没有抛出异常:
[pool-1-thread-1] This task runs for 1 second
[pool-1-thread-5] This task runs for 5 seconds
[pool-1-thread-4] This task runs for 4 seconds
[pool-1-thread-2] This task runs for 2 seconds
[pool-1-thread-3] This task runs for 3 seconds
[pool-1-thread-1] Task completed!
[pool-1-thread-2] Task completed!
[pool-1-thread-3] Task completed!
[pool-1-thread-4] Task completed!
[pool-1-thread-5] Task completed!
Process finished with exit code 0
还有一件事你应该仔细考虑。正如您在问题中提到的,您的任务是执行MySQL查询。请记住,如果您的任务终止,并不意味着查询执行已停止 - 它只表示服务器在5-6秒内没有响应,但很可能查询仍在执行中。在这种情况下,您可能会遇到错误的假设,即在花费更多时间的情况下任务未完成,但最终执行了MySQL服务器查询,但没有结果返回到您的任务。另一件事是,在这种情况下,您失去了对数据库提交事务的控制权,这在您的情况下可能至关重要。我希望它能帮助您更好地了解什么是您的问题的最佳解决方案。祝你好运!