[英]Thread safety issue while calling method in parallel
我有一个库,客户在其中传递一个DataRequest
对象,该对象的信息包含用户ID和其他字段。 我们使用该DataRequest
对象对两个不同的REST服务进行HTTP调用,然后创建一个DataResponse
对象,并将其返回给客户。 我的库中有一个全局级别的超时,该超时适用于HTTP调用,并且如果调用超时,那么在制作DataResponse
对象时,我们仅将超时错误消息返回给客户。
给定一个DataRequest
对象,我将对服务进行HTTP调用,这将给我一些东西,然后在此基础上创建List,然后对于每个DataRequest
对象,我将在与getSyncData
方法中相同的全局超时中并行调用performDataRequest
方法然后使List<DataResponse>
对象并返回响应。
下面是我的DataClient
类,客户将通过传递DataRequest
对象来调用它:
public class DataClient implements Client {
private RestTemplate restTemplate = new RestTemplate();
private ExecutorService service = Executors.newFixedThreadPool(15);
@Override
public List<DataResponse> getSyncData(DataRequest key) {
List<DataResponse> response = new ArrayList<DataResponse>();
Future<List<DataResponse>> responseFuture = null;
try {
responseFuture = getAsyncData(key);
response = responseFuture.get(key.getTimeout(), key.getTimeoutUnit());
} catch (TimeoutException ex) {
response.add(new DataResponse(DataErrorEnum.CLIENT_TIMEOUT, DataStatusEnum.ERROR));
responseFuture.cancel(true); // terminating the tasks that have got timed out
// logging exception here
}
return response;
}
@Override
public Future<List<DataResponse>> getAsyncData(DataRequest key) {
DataFetcherTask task = new DataFetcherTask(key, restTemplate);
Future<List<DataResponse>> future = service.submit(task);
return future;
}
}
下面是我的DataFetcherTask
类,它完成所有工作:
public class DataFetcherTask implements Callable<List<DataResponse>> {
private DataRequest key;
private RestTemplate restTemplate;
private ExecutorService executorService = Executors.newFixedThreadPool(10);
public DataFetcherTask(DataRequest key, RestTemplate restTemplate) {
this.key = key;
this.restTemplate = restTemplate;
}
@Override
public List<DataResponse> call() throws Exception {
List<DataRequest> keys = performKeyRequest();
List<Future<DataResponse>> responseFutureList = new ArrayList<Future<DataResponse>>();
for (final DataRequest key : keys) {
responseFutureList.add(executorService.submit(new Callable<DataResponse>() {
@Override
public DataResponse call() throws Exception {
return performDataRequest(key);
}
}));
}
List<DataResponse> responseList = new ArrayList<DataResponse>();
for (Future<DataResponse> future : responseFutureList) {
responseList.add(future.get());
}
return responseList;
}
private List<DataRequest> performKeyRequest() {
List<DataRequest> keys = new ArrayList<>();
// use key object which is passed in contructor to make HTTP call to another service
// and then make List of DataRequest object and return keys.
// max size of keys list will be three.
return keys;
}
private DataResponse performDataRequest(DataRequest key) {
Mappings mappings = ShardMapping.getMappings(key.getType());
List<String> hostnames = mappings.getAllHostnames(key);
for (String hostname : hostnames) {
if (DataUtils.isEmpty(hostname) || ShardMapping.isBlockHost(hostname)) {
continue;
}
try {
String url = generateUrl(hostname);
URI uri = URI.create(url);
ResponseEntity<String> response = restTemplate.exchange(uri, HttpMethod.GET, key.getEntity(), String.class);
ShardMapping.unblockHost(hostname);
if (response.getStatusCode() == HttpStatus.NO_CONTENT) {
return new DataResponse(response.getBody(), DataErrorEnum.NO_CONTENT,
DataStatusEnum.SUCCESS);
} else {
return new DataResponse(response.getBody(), DataErrorEnum.OK, DataStatusEnum.SUCCESS);
}
} catch (HttpClientErrorException | HttpServerErrorException ex) {
HttpStatusCodeException httpException = ex;
DataErrorEnum error = DataErrorEnum.getErrorEnumByException(httpException);
String errorMessage = httpException.getResponseBodyAsString();
return new DataResponse(errorMessage, error, DataStatusEnum.ERROR);
// logging exception here
} catch (RestClientException ex) {
ShardMapping.blockHost(hostname);
// logging exception here
}
}
return new DataResponse(DataErrorEnum.SERVICE_UNAVAILABLE, DataStatusEnum.ERROR);
}
}
探针声明:-
performDataRequest
方法的方式是否安全? call
使用call
方法来完成这项工作感到很奇怪吗? 为此,我还有两个执行器,一个在具有15个线程的DataClient
类内部,另一个在具有10个线程的DataFetcherTask
类中。 不确定这是否是正确的方法? 有什么更好的办法吗? 我的代码线程与从call方法并行调用performDataRequest方法的方式是否安全?
通常,但不是完全。 一个线程可以在另一个线程调用ShardMapping.getMapping()
修改ShardMapping
吗? 例如, ShardMapping.unblockHost()
修改ShardMapping
? 如果是这样,那么如果两个线程试图同时调用ShardMapping.unblockHost()
,您将ShardMapping.unblockHost()
。 那有意义吗?
解决方法是使PerformDataRequest()
仅执行HTTP请求,而不执行ShardMapping
逻辑。 像这样:
private DataResponse performDataRequest(URI uri, DataRequest key) {
try {
ResponseEntity<String> response = restTemplate.exchange(uri, HttpMethod.GET, key.getEntity(), String.class);
if (response.getStatusCode() == HttpStatus.NO_CONTENT) {
return new DataResponse(response.getBody(), DataErrorEnum.NO_CONTENT,
DataStatusEnum.SUCCESS);
} else {
return new DataResponse(response.getBody(), DataErrorEnum.OK, DataStatusEnum.SUCCESS);
}
} catch (HttpClientErrorException | HttpServerErrorException ex) {
HttpStatusCodeException httpException = ex;
DataErrorEnum error = DataErrorEnum.getErrorEnumByException(httpException);
String errorMessage = httpException.getResponseBodyAsString();
return new DataResponse(errorMessage, error, DataStatusEnum.ERROR);
// logging exception here
} catch (RestClientException ex) {
return null;
// logging exception here
}
}
然后将ShardMapping
代码移出将来,在for (final DataRequest key : keys) {
循环中。
其次,在另一个调用中使用call方法来完成这项工作感到很奇怪吗? 为此,我还有两个执行器,一个在具有15个线程的DataClient类内部,另一个在具有10个线程的DataFetcherTask类中。 不确定这是否是正确的方法? 有什么更好的办法吗?
那有点傻,而且不是最好的方法。 现在,您的设置如下所示:
+----------------- performDataRequest()
|
max 3 sec |
getAsyncData --- DataFetcherTask ----------- performDataRequest()
|
|
+----------------- performDataRequest()
相反,为什么不将3秒超时放在performDataRequest()
将来上,然后正常调用DataFetcherTask.call()
?
max 3 sec
+----------------- performDataRequest()
|
| max 3 sec
DataFetcherTask ----------- performDataRequest()
|
| max 3 sec
+----------------- performDataRequest()
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.