What is the correct way to implement synchronization and asynchrony methods in a library?

I need to create a library in which I will have a synchronous and asynchronous function.

  • executeSynchronous() - waits until I have a result, returns the result.
  • executeAsynchronous() - immediately returns the Future, which can be processed after performing other actions, if necessary.

The main logic of my library

The client will use our library, and they will call it by passing the DataKey builder object. Then we will create a URL using this DataKey and make an HTTP client for that URL by executing it, and after we get the response as a JSON string, we will send this JSON string to our client, since this is created DataResponse object. Some clients will call executeSynchronous() , and some may call the executeAsynchronous() method, so I have to provide two methods separately in my library.

Interface:

 public interface Client { // for synchronous public DataResponse executeSynchronous(DataKey key); // for asynchronous public Future<DataResponse> executeAsynchronous(DataKey key); } 

And then I have my DataClient that implements the above Client interface:

 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; } } 

A simple class that will perform the actual task:

 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; } } 

A client in our company will use my library, as shown below, using my factory in its code base -

 // 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); 

What is the best way to implement the sync and async method for my library? Does sync call as async + waiting implement a bad idea? Because it will consume one thread from the thread pool for each call with my setting in real time? If so, can anyone explain why this is a bad idea, and will it have any performance issues?

How will you apply the sync and async method, given the above criteria? What is the best way to do this? This library will be used under very heavy load, and it should be fast, that is, it should take the time my server responds.

Should I use AsyncRestTemplate in my code base, which will be an asynchronous non-blocking architecture?

+6
source share
5 answers

If you create a new thread even in the case of synchronous operation (when it is not really needed), this will lead to a performance hit. You basically create a new topic (read as a waste of resources) without even getting any benefits from it. In doing so, I think the best way is to wrap the HTTP part in another class. This way you will reuse the code for HTTP access in both synchronous and asynchronous cases.

 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; } } 

Now to implement the client, just use this class.

 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); } }); } } 

Thus, your HTTP implementation is completely separate in the future, if you need to change the way you get the DataResponse (possibly from a DB call), then you only need to change the HTTPAccess class and the other part will not be affected.

+2
source

For a synchronous call, running a separate thread is definitely not a good idea. In this case, you bear the additional costs and resources for Thread along with the cost of contextual thread switching.

If there are many synchronous calls, then you will unnecessarily block threads for asynchronous calls, since your executor has fixed-sized threads. In this case, the total system bandwidth will be less.

For example: If there are 10 clients calling each of the synchronous and asynchronous calls, only threads will actually work in your implementation. However, if you must also use client threads and not make a synchronous call as asynchronous and wait, then all 20 calls will be processed at the same time.

+4
source

I think this is better:

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

It does the same job, is clear, shorter, and has no overhead.

Please note that it also cleans up the duplicate Exception handling that you currently have.

If the timeout should , the option should use the base timeouts for the RestTemplate class, as described in Spring. RestTemplate timeout

Then the timeout will raise a RestClientException , which you or the library client can handle.

+1
source

I would not worry about this Task class. Just make your synchronous method do all the work and call it asynchronously from the asynchronous method.

 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); } }); } } 
+1
source

The above code for executing a synchronous task through async is similar to how all async does. If this is a requirement, I suggest you use google guava ListenableFuture. I am not a supporter, but he has methods for controlling the task timeout, well-written callbacks for processing onSuccess, onFailure scripts. https://code.google.com/p/guava-libraries/wiki/ListenableFutureExplained

+1
source

Source: https://habr.com/ru/post/984303/


All Articles