Java concurrency — Executor service with a real-world example

Multithreading could be overwhelming at first glance but it plays an essential role in improving application efficiency.

In this article, I’ll take you on a walk with Java’s Executor service which simplifies multithreading, through a real-world example where we fetch data from Alphavantage service which returns daily time series (date, daily open, daily high, daily low, daily close, daily volume) of the global equity specified.

Before we dive into Executor Service, it is better to have an understanding of the following terms — Threads, ThreadPool, Runnable interface, and Callable interface.

Task: Our application requires daily time-series data of the global equity specified (GOOGL, AAPL, etc.) to apply our business logic on the fetched data. There are various service providers who provide API for returning the required stock quote. For our implementation, we have considered Alphavantage service — (https://www.alphavantage.co/documentation/). We are required to write the logic to fetch daily time-series data for various tickers (GOOGL, AAPL, etc.) and store it in a Java ArrayList which could be further used by our functions implementing the business logic.

Note: Given a ticker to the Alphavantage service demo (free API key), it returns the stock quote for the last 100 working days for the respective ticker.

To understand the power of multithreading, first, let's consider a scenario where we do our task as a single-threaded operation.

The following main method will be used to call the respective methods to retrieve the stock quote without multithreading — getResponseList() and with multithreading getResponseListParellel()

The following are additional methods we will be using to send the request to Alphavantage service to retrieve the data. We are using RestTemplate which is a part of Spring-Web to get the data from Alphavantage service

Now that we have the boilerplate code established, let's dive into the actual implementation of fetching the data without multithreading.

In the above method, we call the alphavantageResponse() method sequentially for each ticker. When the main thread reaches — restTemplate.getForObject(urlRequired, String.class), it stops the further execution unless the complete response is obtained from the Alphavantage service for that particular ticker. Once the complete response is received, the thread proceeds further to add the required response to the “responses” list and then we move to the next ticker in the “symbols” list.

What happens when the thread waits for the response from our service — The application goes into an idle state where the resources remain unutilized just like the typical scenario of CPU idling during I/O operation.

Now, How do we tackle this, and yes you are right :) — “Multithreading”

When we execute our Java Application, the main thread is provided to us by the JVM (Java virtual machine). At this point, this thread is the single unit of execution of our program, and creating such multiple threads is where multithreading comes into the picture.

Java provides multiple ways by which we can achieve multithreading (create multiple threads) but in this article, our focus remains on the Executor Service. The other ways are to extend the Thread class or to implement the runnable interface.

The following code snippet is the getResponseList() method refactored to achieve concurrency in fetching the data from Alphavantage service.

In practice, creating threads and managing them is not a simple task and could increase the program complexity and could often lead to deadlocks and race conditions if not developed properly. Executors framework provided by Java eases the process of thread creation, management, and task submission by abstracting the underlying process to achieve concurrency.

In the above code snippet, we create a thread pool of 3 threads using the factory method of the Executors class — ExecutorService service = Executors.newFixedThreadPool(3). Now, we have our ExecutorService “service” ready to execute tasks.

Java has two kins of tasks — Runnable and the Callable. The Runnable task does not return a value whereas the Callable task supports returning a value. In our example, we are returning the String response from the Alphavantage service and hence our implementation passes a Callable task to our service.

The ExecutorService object has access to a submit() method to which we can pass our Callable task. Using the lambda expression we override the call() method of Callable interface to return the value required which in our case is the value returned by alphavantageResponse() method.

When the submit() method is invoked, the Executors framework creates the threads according to the value in the thread pool passed to the ExcutorService. These threads work concurrently to execute the task specified and fetch the result.

In the for loop above, we loop through the symbols list and pass the symbol one by one to the alphavantageResponse(symbols.get(k)) method which is now passed as a Callable task to the ExecutorService. What this means is that we will now be using multiple threads sending a request to the Alphavantage service API concurrently rather than in a sequential manner. The invocation of alphavantageResponse(symbols.get(k)) method won’t halt the execution of the program to wait for the response back from Alphavantage service i.e we will keep looping through our symbols list and regardless of the response received from the previous method invocation, a new thread from the thread pool will now invoke the alphavantageResponse(symbols.get(k)) again.

The big question here now is — what is Future<String> future. To keep things simple, consider Future as a promise. The Future object acts as a placeholder for something that is not yet available but will be available to us in the future, in our case which is the response data that isn’t available immediately but is getting fetched from the Alphavantage service API. As mentioned earlier, the Callable task returns a value and this value requires a store. The submit() method called on the ExecutorService returns an object of Future type holding the return type of our method alphavantageResponse().

After looping through the symbols list, what we have with us is another List of Future<String>objects — “allFutures”, which now holds the Future objects of the respective symbol from the symbols list. The threads in the background are fetching the data from the API concurrently.

The Future object has access to isDone() and get() method. Using the isDone() method we can check if the thread has completed fetching the response or not and get() is a blocking method that blocks the asynchronous execution until the result is ready.

In the above snippet, we loop through the “allFutures” list holding the Future objects. This is where we extract the response data stored in the Future<String> object into a String — String stockQuote = future.get(). The get() method blocks the asynchronous task and waits until the result for the future in current context is completely gathered. A combination of get() and isDone() could be used to even further improve concurrency.

Finally, at the end of this loop, we have all our results stored in the “responses” list and we can shut down our Executor service and return the “responses” list.

The following snip shows the benchmarking of time taken for sequential execution vs concurrent execution in ms. We can clearly state the benefits and the power multithreading gives us to improve our applications.

The complete code: https://github.com/Meet11/javaMultithreadingExample

Replace the API key in the buildUrl() method with your API key which can be obtained from https://www.alphavantage.co/documentation/

Note: This is a Spring application and will require the necessary Spring — Web initialization.

Thank You for reading! :D —

Meet Gujrathi

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store