为分配给线程池java

时间:2017-07-17 06:02:40

标签: java multithreading executorservice

您好我有关于分配给线程池java的任务的超时问题。

详细说明:

  1. 我已经实现了一个API,它并行运行一些查询并返回响应。
  2. 通过Executors.newFixedThreadPool(40)创建了一个固定的线程池。每当有人调用此API时,一组10个任务就是此线程池的调度。这些任务在内部对Mysql执行一组查询。
  3. 我必须返回API的响应,SLA为6-7秒。所以我必须为线程池上安排的所有10个任务创建一个超时。我知道如何超时单个任务(它会抛出一个中断的异常,虽然该线程只有在启动后才会完成)。
  4. 我也不想重载线程池。因此,我为通过分配给此线程池的任务运行的所有查询创建了60秒的超时。因此,线程可以在一分钟内自由地执行另一项任务。
  5. 问题:

    有没有一种优雅的方法来解决这个问题?

    @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秒。

2 个答案:

答案 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服务器查询,但没有结果返回到您的任务。另一件事是,在这种情况下,您失去了对数据库提交事务的控制权,这在您的情况下可能至关重要。我希望它能帮助您更好地了解什么是您的问题的最佳解决方案。祝你好运!