Notes on Spring Tutorial: Creating Asynchronous Methods
Some notes taken during following the “Creating Asynchronous Methods” tutorial.
Spring
Application Context
According to the documentation of Spring:
The interface
org.springframework.context.ApplicationContext
represents the Spring IoC container and is responsible for instantiating, configuring, and assembling beans.
The high-level view of Spring:
The configuration metadata can be represented in XML, Java annotations (e.g. @Autowired
), or Java code.
CommandLineRunner
- The
SpringApplication
class provides a convenient way to bootstrap a Spring application that is started from amain()
method. - The
CommandLineRunner
interface offers arun
method, which is called just beforeSpringApplication.run(…)
completes.
Asynchrony
In a dictionary, the word “synchronous” means “happening, existing, or arising at precisely the same time”, while “asynchronous” means the opposite.
However, in the context of programming, the words may have different meanings. Quoted from Stack Overflow:
Synchronous or Synchronized means “connected”, or “dependent” in some way. In other words, two synchronous tasks must be aware of one another, and one task must execute in some way that is dependent on the other, such as wait to start until the other task has completed. Asynchronous means they are totally independent and neither one must consider the other in any way, either in the initiation or in execution.
Therefore, synchronous tasks may not happen at the same time, but asynchronous tasks may, which conflicts with the definition in a dictionary.
Sometimes the concept of asynchrony and multithreading come in the same context, although they are two different things. This answer on Stack Overflow explains the difference very well with a demonstration.
Thread Pool
In programming, multithreading can be achieved in multiple ways. One way to do so is by using a thread pool, which maintains a pool of threads. When there are no tasks, these threads will be in the waiting state. When a task is submitted, and there are available threads, one thread will be assigned to take care of this task. If all threads are busy with other tasks, the new task will be queued, or we can create a new thread for it.
The figure below depicts the task queue and the thread pool. (Source: Wikipedia)
In Java, using thread pool sometimes involves some other concepts:
Interface Executor
: executes submitted Runnable tasks. It decouples task submission from task execution.Interface Runnable
: should be implemented by any class whose instances are intended to be executed by a thread.
Thread Pools are the most common kind of executor implementation.
Future
A Future
represents the result of an asynchronous computation.
Here is a simple example: (Source: Baeldung)
public class SquareCalculator {
private ExecutorService executor
= Executors.newSingleThreadExecutor();
public Future<Integer> calculate(Integer input) {
return executor.submit(() -> {
Thread.sleep(1000);
return input * input;
});
}
}
Future<Integer> future = new SquareCalculator().calculate(10);
while(!future.isDone()) {
System.out.println("Calculating...");
Thread.sleep(300);
}
Integer result = future.get();
The method get
is used to retrieve the result from the future.
It is a blocking method: it will wait until the result of the asynchronous computation is available.
(In the example above the get
method will return right away because of the isDone
check before it.)
Completable Future
A Completable Future
is a Future
that may be explicitly completed, and may be used as a CompletionStage
,
enabling us to chain operations together.
Examples: (Source: Baeldung)
CompletableFuture<String> completableFuture
= CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future = completableFuture
.thenApply(s -> s + " World");
assertEquals("Hello World", future.get());
CompletableFuture<String> completableFuture
= CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<Void> future = completableFuture
.thenAccept(s -> System.out.println("Computation returned: " + s));
future.get();
Tunning the application
In AppRunner
, we added three more users for findUser
.
The thread pool size was also increased to 3.
The snippet of the log:
2021-05-11 22:06:56.045 INFO 20008 --- [ GithubLookup-3] c.e.asyncmethod.GitHubLookupService : Looking up Spring-Projects
2021-05-11 22:06:56.045 INFO 20008 --- [ GithubLookup-1] c.e.asyncmethod.GitHubLookupService : Looking up PivotalSoftware
2021-05-11 22:06:56.045 INFO 20008 --- [ GithubLookup-2] c.e.asyncmethod.GitHubLookupService : Looking up CloudFoundry
2021-05-11 22:06:57.602 INFO 20008 --- [ GithubLookup-3] c.e.asyncmethod.GitHubLookupService : Looking up Google
2021-05-11 22:06:57.602 INFO 20008 --- [ GithubLookup-1] c.e.asyncmethod.GitHubLookupService : Looking up Microsoft
2021-05-11 22:06:57.602 INFO 20008 --- [ GithubLookup-2] c.e.asyncmethod.GitHubLookupService : Looking up Apple
2021-05-11 22:06:58.831 INFO 20008 --- [ main] com.example.asyncmethod.AppRunner : Elapsed time: 2797
2021-05-11 22:06:58.831 INFO 20008 --- [ main] com.example.asyncmethod.AppRunner : --> User [name=Pivotal Software, Inc., blog=http://pivotal.io]
2021-05-11 22:06:58.831 INFO 20008 --- [ main] com.example.asyncmethod.AppRunner : --> User [name=Cloud Foundry, blog=https://www.cloudfoundry.org/]
2021-05-11 22:06:58.831 INFO 20008 --- [ main] com.example.asyncmethod.AppRunner : --> User [name=Spring, blog=https://spring.io/projects]
2021-05-11 22:06:58.831 INFO 20008 --- [ main] com.example.asyncmethod.AppRunner : --> User [name=Google, blog=https://opensource.google/]
2021-05-11 22:06:58.832 INFO 20008 --- [ main] com.example.asyncmethod.AppRunner : --> User [name=Microsoft, blog=https://opensource.microsoft.com]
2021-05-11 22:06:58.832 INFO 20008 --- [ main] com.example.asyncmethod.AppRunner : --> User [name=Apple, blog=https://apple.com]
Observations:
- The lookup was performed by 3 (instead of 6) dedicated threads.
- The order of lookup wasn’t necessarily the same as in the source code.
The annotation @Async
indicates that the annotated method should run on a separate thread.
The @EnableAsync
annotation switches on Spring’s ability to run @Async
methods in a background thread pool.
If we disable the @Async
annotation, the log would be:
2021-05-11 22:08:20.260 INFO 20024 --- [ main] c.e.asyncmethod.GitHubLookupService : Looking up PivotalSoftware
2021-05-11 22:08:21.812 INFO 20024 --- [ main] c.e.asyncmethod.GitHubLookupService : Looking up CloudFoundry
2021-05-11 22:08:22.964 INFO 20024 --- [ main] c.e.asyncmethod.GitHubLookupService : Looking up Spring-Projects
2021-05-11 22:08:24.124 INFO 20024 --- [ main] c.e.asyncmethod.GitHubLookupService : Looking up Google
2021-05-11 22:08:25.282 INFO 20024 --- [ main] c.e.asyncmethod.GitHubLookupService : Looking up Microsoft
2021-05-11 22:08:26.468 INFO 20024 --- [ main] c.e.asyncmethod.GitHubLookupService : Looking up Apple
2021-05-11 22:08:27.624 INFO 20024 --- [ main] com.example.asyncmethod.AppRunner : Elapsed time: 7364
2021-05-11 22:08:27.624 INFO 20024 --- [ main] com.example.asyncmethod.AppRunner : --> User [name=Pivotal Software, Inc., blog=http://pivotal.io]
2021-05-11 22:08:27.624 INFO 20024 --- [ main] com.example.asyncmethod.AppRunner : --> User [name=Cloud Foundry, blog=https://www.cloudfoundry.org/]
2021-05-11 22:08:27.624 INFO 20024 --- [ main] com.example.asyncmethod.AppRunner : --> User [name=Spring, blog=https://spring.io/projects]
2021-05-11 22:08:27.624 INFO 20024 --- [ main] com.example.asyncmethod.AppRunner : --> User [name=Google, blog=https://opensource.google/]
2021-05-11 22:08:27.625 INFO 20024 --- [ main] com.example.asyncmethod.AppRunner : --> User [name=Microsoft, blog=https://opensource.microsoft.com]
2021-05-11 22:08:27.625 INFO 20024 --- [ main] com.example.asyncmethod.AppRunner : --> User [name=Apple, blog=https://apple.com]
Observations:
- There was only one thread (i.e. the main thread) throughout the execution.
- As a result, the elapsed time increased significantly.
- The order of lookup was the same as in the source code.
References and further reading
[1] What’s the difference between @Component, @Repository & @Service annotations in Spring?
[2] Asynchronous vs synchronous execution, what does it really mean?
[3] What is the difference between concurrency, parallelism and asynchronous methods?
[4] What is the difference between asynchronous programming and multithreading?
[5] Difference between CompletableFuture, Future and RxJava’s Observable