如何在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
因此您已经捕获它们。
如果我捕获
Exception
或RuntimeException
什么区别?
捕获Exception
捕获已检查(非运行时)exception和运行时exception。 仅捕获RuntimeException
将意味着任何已定义的exception都不会被捕获并且将被该方法抛出。
RuntimeException
是特殊的exception,不需要由代码检查。 例如,任何代码都可以抛出IllegalArgumentException
而不将方法定义为throws IllegalArgumentException
。 对于已检查的exception,如果调用者方法没有捕获或抛出已检查的exception,则编译器错误,但RuntimeException
不是这样。
这是一个关于这个主题的好答案:
- Java:已检查vs未经检查的exception说明