在库中实现同步和异步方法的正确方法是什么?

时间:2015-03-27 06:28:48

标签: java multithreading performance executorservice resttemplate

我需要创建一个库,我将在其中具有同步和异步功能。

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

我图书馆的核心逻辑

客户将使用我们的库,他们将通过传递DataKey构建器对象来调用它。然后,我们将使用该DataKey对象构造一个URL,并通过执行它来对该URL进行HTTP客户端调用,然后在我们将响应作为JSON字符串返回之后,我们将该JSON字符串发送回给我们的客户。它是通过创建DataResponse对象。有些客户会调用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();
    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);
        } 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) {
            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;
    }
}

我们公司内的客户将使用我的库,如下所示,在我们的代码库中使用我的工厂 -

// if they are calling `executeSynchronous()` method
DataResponse response = DataClientFactory.getInstance().executeSynchronous(dataKey);

// and if they want to call `executeAsynchronous()` method
Future<DataResponse> response = DataClientFactory.getInstance().executeAsynchronous(dataKey);

为我的库实现同步和异步方法的最佳方法是什么?实施sync call as async + waiting是不是一个坏主意?因为使用我的currrent设置,每次调用它会从线程池中消耗一个线程吗?如果是,那么任何人都可以解释为什么这是一个坏主意,它会有任何性能问题吗?

根据上述标准,您将如何实现同步和异步方法?做这个的最好方式是什么?这个库将在非常繁重的负载下使用,并且必须很快,这意味着无论我的服务器采取什么响应都需要时间。

我应该在我的代码库中使用AsyncRestTemplate哪个是非同步非阻塞架构?

5 个答案:

答案 0 :(得分:4)

对于synchronous调用,在单独的线程中执行绝对不是一个好主意。 在这种情况下,您需要为线程带来额外的成本和资源以及线程上下文切换的成本。

如果有很多synchronous个调用,那么你将不必要地阻塞asynchronous调用的线程,因为你的执行者是固定大小的线程。在这种情况下,系统的总吞吐量会更少。

例如: 如果有10个客户端调用synchronousasynchronous调用中的每一个,则在您的实现中,只有线程实际上正在工作。但是,如果您还要使用客户端线程而不是synchronous调用asynchronous并等待,那么所有20个调用将同时处理。

答案 1 :(得分:2)

如果您正在创建新线程,即使在同步操作的情况下(实际上不需要),也会导致性能下降。你基本上是创建新的线程(读作浪费资源),甚至没有任何好处。 话虽如此,我认为更好的方法是将HTTP部分包装在不同的类中。这样,您就可以在同步和异步情况下重复使用HTTP访问代码。

class HTTPAccess{
    private RestTemplate restTemplate;
    private DataKey key;

    public HTTPAccess(DataKey key,RestTemplate restTemplate){
        this.key = key;
        this.restTemplate = restTemplate;

    }


    public DataResponse performRequest() {
        DataResponse dataResponse = 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) {
            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;
    }

}

现在,对于客户端实现,只需使用此类。

public class DataClient implements Client {

    private ExecutorService executor = Executors.newFixedThreadPool(10);
    private RestTemplate restTemplate;
    private void initRestClient(DataKey key){
    if(restTemplate == null)
        restTemplate = new RestTemplate(clientHttpRequestFactory(key));
    }

    private ClientHttpRequestFactory clientHttpRequestFactory(DataKey key) {
        HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
        factory.setReadTimeout(key.getTimeout());
        factory.setConnectTimeout(key.getTimeout());
        //if you need to set otherparams this is the place we can do it extracting from DataKey obj
        return factory;
    }

    // for synchronous call
    @Override
    public DataResponse executeSynchronous(DataKey key) {
        initRestClient(key);
        DataResponse dataResponse = new HTTPAccess(key).performRequest();
        return dataResponse;
    }

    //for asynchronous call
    @Override
    public Future<DataResponse> executeAsynchronous(final DataKey key) {
        return executor.submit(new Callable<DataResponse>() {
            @Override
            public DataResponse call() throws Exception {
                return executeSynchronous(key);
            }
        });
    }
}

这样,您的HTTP实现是完全独立的,将来如果您需要更改接收 DataResponse 的方式(可能来自数据库调用),那么您必须仅更改HTTPAccess类和其他部分不会受到影响。

答案 2 :(得分:1)

我认为这更好:

@Override
public DataResponse executeSynchronous(DataKey key) {
    Task task = new Task(key, restTemplate);
    return task.call();
}

它执行相同的工作,清晰,更短,并且没有开销。

请注意,他还会清除您当前拥有的重复异常处理。

如果超时是必须,则选项是使用RestTemplate类的基础超时,如Spring RestTemplate timeout

中所述

然后超时将导致您或库客户端可以处理的RestClientException

答案 3 :(得分:1)

我不打扰那个Task类。只需让你的同步方法完成所有工作,并从异步方法中异步调用它。

public class DataClient implements Client {

    private RestTemplate restTemplate = new RestTemplate();
    private ExecutorService executor = Executors.newFixedThreadPool(10);

    // for synchronous call
    @Override
    public DataResponse executeSynchronous(DataKey key) {
        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) {
            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(final DataKey key) {
        return executor.submit(new Callable<DataResponse>() {
            @Override
            public DataResponse call() throws Exception {
                return executeSynchronous(key);
            }
        });
    }
}

答案 4 :(得分:1)

上述通过异步执行同步任务的代码与将所有内容都设置为异步相同。如果这是要求,那么我建议你使用google guava的ListenableFuture。我不是一个拥护者,但它有管理任务超时的方法,编写好的回调来处理onSuccess,onFailure场景。 https://code.google.com/p/guava-libraries/wiki/ListenableFutureExplained