并行调用方法时线程安全问题

时间:2015-12-15 23:03:40

标签: java multithreading thread-safety executorservice callable

我有一个库,其中客户正在传递一个DataRequest对象,该对象包含包含用户ID和其他字段的信息。我们使用DataRequest对象对两个不同的REST服务进行HTTP调用,然后创建一个DataResponse对象并将其返回给客户。我在我的库中有一个全局级别超时,它应用于两个HTTP调用,如果调用得到timedout,那么我们只需返回带有超时错误消息给客户同时制作DataResponse对象。

给定一个DataRequest对象,我将对一个服务进行HTTP调用,这将给我一些东西,然后根据我将生成List,然后对于每个DataRequest对象,我将调用{{我在performDataRequest方法中使用相同的全局超时并行方法,然后生成getSyncData对象并返回响应。

以下是我的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

Probem声明: -

  • 我的代码线程是否安全,我从调用方法并行调用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个线程。不确定这是否是正确的方法?还有更好的办法吗?

1 个答案:

答案 0 :(得分:1)

  

我的代码线程是否与我调用performDataRequest的方式一样安全   调用方法并行的方法?

主要是,但并非完全。当另一个线程正在调用ShardMapping时,一个线程是否可以修改ShardMapping.getMapping()?例如,ShardMapping.unblockHost()修改ShardMapping吗?如果是这样,如果两个线程同时尝试呼叫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方法感觉很奇怪   打电话做这个工作?为此我也有两个执行者,   一个内部DataClient类,有15个线程,另一个在   DataFetcherTask类有10个线程。不确定这是不是   正确的方法吗?还有更好的办法吗?

这有点傻,而且不是最好的方法。现在,您的设置如下所示:

                          +----------------- performDataRequest()
                          |
         max 3 sec        |       
getAsyncData --- DataFetcherTask ----------- performDataRequest()
                          |
                          |
                          +----------------- performDataRequest()

相反,为什么不在performDataRequest()的未来放置3秒超时,然后只是正常拨打DataFetcherTask.call()

                                  max 3 sec
                          +----------------- performDataRequest()
                          |
                          |       max 3 sec
                 DataFetcherTask ----------- performDataRequest()
                          |
                          |       max 3 sec
                          +----------------- performDataRequest()