如何在multithreading环境中更好地使用ExecutorService?

我需要创建一个库,在其中我将有同步和异步方法。

  • executeSynchronous() – 等到我有结果,返回结果。
  • executeAsynchronous() – 立即返回一个Future,如果需要,可以在其他事情完成后处理。

我的图书馆的核心逻辑

客户将使用我们的库,他们将通过传递DataKey构建器对象来调用它。 然后,我们将使用该DataKey对象构造一个URL,并通过执行它来对该URL进行HTTP客户端调用,然后在我们将响应作为JSON字符串返回之后,我们将通过创建将该JSON字符串发送回我们的客户DataResponse对象。 有些客户会调用executeSynchronous() ,有些可能会调用executeAsynchronous() ,这就是为什么我需要在我的库中单独提供两个方法。

接口:

 public interface Client { // for synchronous public DataResponse executeSynchronous(DataKey key); // for asynchronous public Future executeAsynchronous(DataKey key); } 

然后我有我的DataClient实现上面的Client接口:

 public class DataClient implements Client { private RestTemplate restTemplate = new RestTemplate(); // do I need to have all threads as non-daemon or I can have daemon thread for my use case? private ExecutorService executor = Executors.newFixedThreadPool(10); // for synchronous call @Override public DataResponse executeSynchronous(DataKey key) { DataResponse dataResponse = null; Future future = null; try { future = executeAsynchronous(key); dataResponse = future.get(key.getTimeout(), TimeUnit.MILLISECONDS); } catch (TimeoutException ex) { PotoLogging.logErrors(ex, DataErrorEnum.TIMEOUT_ON_CLIENT, key); dataResponse = new DataResponse(null, DataErrorEnum.TIMEOUT_ON_CLIENT, DataStatusEnum.ERROR); future.cancel(true); // terminating tasks that have timed out } catch (Exception ex) { PotoLogging.logErrors(ex, DataErrorEnum.CLIENT_ERROR, key); dataResponse = new DataResponse(null, DataErrorEnum.CLIENT_ERROR, DataStatusEnum.ERROR); } return dataResponse; } //for asynchronous call @Override public Future executeAsynchronous(DataKey key) { Future future = null; try { Task task = new Task(key, restTemplate); future = executor.submit(task); } catch (Exception ex) { PotoLogging.logErrors(ex, DataErrorEnum.CLIENT_ERROR, key); } return future; } } 

将执行实际任务的简单类:

 public class Task implements Callable { private DataKey key; private RestTemplate restTemplate; public Task(DataKey key, RestTemplate restTemplate) { this.key = key; this.restTemplate = restTemplate; } @Override public DataResponse call() { DataResponse dataResponse = null; String response = null; try { String url = createURL(); response = restTemplate.getForObject(url, String.class); // it is a successful response dataResponse = new DataResponse(response, DataErrorEnum.NONE, DataStatusEnum.SUCCESS); } catch (RestClientException ex) { PotoLogging.logErrors(ex, DataErrorEnum.SERVER_DOWN, key); dataResponse = new DataResponse(null, DataErrorEnum.SERVER_DOWN, DataStatusEnum.ERROR); } catch (Exception ex) { // should I catch RuntimeException or just Exception here? PotoLogging.logErrors(ex, DataErrorEnum.CLIENT_ERROR, key); dataResponse = new DataResponse(null, DataErrorEnum.CLIENT_ERROR, DataStatusEnum.ERROR); } return dataResponse; } // create a URL by using key object private String createURL() { String url = somecode; return url; } } 

我对上述解决方案几乎没有疑问 –

  • 我应该在上述用例中使用守护程序还是非守护程序线程?
  • 此外,我正在终止已经超时的任务,以便它不会长时间占用我有限的10个线程之一。 这看起来像我做的那样吗?
  • 在我的call()方法中,我正在捕获exception。 我应该在那里捕获RuntimeException吗? 如果我捕获Exception或RuntimeException有什么区别?

当我开始研究这个解决方案时,我并没有终止已经超时的任务。 我向客户端报告超时,但任务继续在线程池中运行(可能长时间占用我有限的10个线程之一)。 所以我在网上进行了一些研究,我发现我可以通过在将来使用取消来取消已经超时的任务,如下所示 –

 future.cancel(true); 

但我想确保,在我的executeSynchronous方法中取消已经超时的任务的方式是否正确?

因为我在Future上调用cancel() ,如果任务仍然在队列中,它将阻止它运行,所以我不确定我在做什么是对还是不对? 这样做的正确方法是什么?

如果有更好的方法,那么任何人都能提供一个例子吗?

我们是否应该永远终止已经超时的任务? 如果我们不这样做那么可能会产生什么影响?

我应该在上述用例中使用守护程序还是非守护程序线程?

这取决于。 但在这种情况下,我更喜欢守护程序线程,因为使用允许进程退出的客户端很方便。

这看起来像我做的那样吗?

不,它没有。 中断IO任务非常困难。 尝试在RestTemplate中设置超时。 在这种情况下取​​消未来似乎毫无意义。

如果我捕获Exception或RuntimeException有什么区别?

如果你没有在try块中检查exception,则没有区别:)只是因为在这种情况下只有RuntimeExceptions可能。

还有一个重要的注意事项:将同步调用实现为异步+等待是个坏主意。 它没有意义,每次调用都会从线程池中消耗一个线程。 只需创建Task的实例并在当前线程中调用它!

我应该在上述用例中使用守护程序还是非守护程序线程?

这取决于您是否希望这些线程停止程序退出。 当最后一个非守护程序线程完成时,JVM将退出。

如果JVM存在,那么任何时候都可以杀死这些任务,那么它们应该是守护进程。 如果您希望JVM等待它们,那么将它们设为非守护进程。

请参阅: java守护程序线程和非守护程序线程

此外,我正在终止已经超时的任务,以便它不会长时间占用我有限的10个线程之一。 这看起来像我做的那样吗?

是的,不是。 你正在调用Future上的cancel() ,如果它仍然在队列中,它将阻止它运行。 但是,如果线程已在运行任务,则取消将中断线程。 有可能restTemplate调用不可中断,因此中断将被忽略。 只有某些方法(如Thread.sleep(...)是可中断的并抛出InterruptException 。所以调用future.cancel(true)不会停止操作并终止线程。

请参阅: 线程没有中断

你可以做的一件事是在你的Task对象上放置一个cancel()方法,强制关闭restTemplate 。 你需要试验一下。 另一个想法是在restTemplate连接或IO上设置某种超时,因此它不会永远等待。

如果您正在使用Spring RestTemplate那么没有直接关闭,但您可以关闭我认为可能通过SimpleClientHttpRequestFactory的底层连接,因此您需要在底层HttpURLConnection上调用disconnect()

在我的call()方法中,我正在捕获exception。 我应该在那里捕获RuntimeException吗?

RuntimeException扩展了Exception因此您已经捕获它们。

如果我捕获ExceptionRuntimeException什么区别?

捕获Exception捕获已检查(非运行时)exception运行时exception。 仅捕获RuntimeException将意味着任何已定义的exception都不会被捕获并且将被该方法抛出。

RuntimeException是特殊的exception,不需要由代码检查。 例如,任何代码都可以抛出IllegalArgumentException而不将方法定义为throws IllegalArgumentException 。 对于已检查的exception,如果调用者方法没有捕获或抛出已检查的exception,则编译器错误,但RuntimeException不是这样。

这是一个关于这个主题的好答案:

  • Java:已检查vs未经检查的exception说明