我需要创建一个库,其中将包含同步和异步方法。
-
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
所以你已经抓住了它们。
如果我抓到
Exception
或RuntimeException
有什么区别?
捕获Exception
会同时捕获已检查(非运行时)异常和运行时异常。 仅捕获RuntimeException
将意味着不会捕获任何定义的异常,并且将由方法引发。
RuntimeException
是不需要代码检查的特殊异常。 例如,任何代码都可以抛出IllegalArgumentException
,而无需将方法定义为 throws IllegalArgumentException
。 对于已检查的异常,如果调用方方法未捕获或抛出已检查的异常,则为编译器错误,但对于 RuntimeException
s,情况并非如此。
这是关于这个话题的一个很好的答案:
- Java:已检查与未检查的异常解释
对于上述用例,我应该使用守护程序还是非守护程序线程?
这要看情况。但在这种情况下,我更喜欢守护进程线程,因为使用允许进程退出的客户端很方便。
这看起来对我的方式正确吗?
不,它没有。中断 IO 任务非常困难。尝试在 RestTemplate 中设置超时。在这种情况下,取消未来似乎毫无意义。
如果我捕获异常或运行时异常有什么区别?
如果您在 try 块中没有选中异常,则没有区别:)只是因为在这种情况下只有运行时异常可能。
还有一个重要的注意事项:将同步调用实现为异步 + 等待是一个坏主意。它毫无意义,每次调用都会消耗线程池中的一个线程。只需创建任务的实例并在当前线程中调用它!