繁体   English   中英

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

[英]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);       
    }
}

探针声明:-

  • 我的代码线程与从call方法并行调用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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM