如何在多线程环境中更好地使用ExecutorService



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

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

我的库的核心逻辑

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

接口:

public interface Client {
    // for synchronous
    public DataResponse executeSynchronous(DataKey key);
    // for asynchronous
    public Future<DataResponse> 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<DataResponse> 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<DataResponse> executeAsynchronous(DataKey key) {
        Future<DataResponse> 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<DataResponse> {
    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()方法中,我正在捕获异常。我应该在那里抓住RuntimeException吗?如果我捕获异常或运行时异常有什么区别?

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

future.cancel(true);

但我想确保,它看起来是否像我在executeSynchronous方法中取消超时任务

的方式正确?

由于我在Future上调用cancel(),如果任务仍在队列中,这将阻止它运行,所以我不确定我正在做的事情是否正确?正确的方法是什么?

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

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

对于上述用例,我应该使用守护程序还是非守护程序线程?

这取决于您是否希望这些线程阻止程序退出。 当最后一个非守护程序线程完成时,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() 方法中,我正在捕获异常。我应该在那里抓住RuntimeException吗?

RuntimeException延伸Exception所以你已经抓住了它们。

如果我抓到ExceptionRuntimeException有什么区别?

捕获Exception会同时捕获已检查(非运行时)异常运行时异常。 仅捕获RuntimeException将意味着不会捕获任何定义的异常,并且将由方法引发。

RuntimeException是不需要代码检查的特殊异常。 例如,任何代码都可以抛出IllegalArgumentException,而无需将方法定义为 throws IllegalArgumentException 。 对于已检查的异常,如果调用方方法未捕获或抛出已检查的异常,则为编译器错误,但对于 RuntimeException s,情况并非如此。

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

  • Java:已检查与未检查的异常解释

对于上述用例,我应该使用守护程序还是非守护程序线程?

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

这看起来对我的方式正确吗?

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

如果我捕获异常或运行时异常有什么区别?

如果您在 try 块中没有选中异常,则没有区别:)只是因为在这种情况下只有运行时异常可能。

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

相关内容

  • 没有找到相关文章

最新更新