Asynchronous invocation is almost a gold mine in dealing with high concurrent Web application performance issues, so what is "asynchronous invocation"?
"Asynchronous Call" corresponds to "Synchronous Call". Synchronous Call refers to a program that executes sequentially in the order defined. Each line of program must wait for the previous line to execute before it can execute. Asynchronous invocation refers to the execution of subsequent programs without waiting for the asynchronous invocation statement to return the result.
Synchronous Call
Here is a simple example to visually understand what synchronous calls are:
Define the Task class, create three processing functions to simulate three operations to execute the task, operation time is taken randomly (within 10 seconds)
@Component
public class Task {
public static Random random =new Random();
public void doTaskOne() throws Exception {
System.out.println("Start Task One");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
System.out.println("Complete Task 1, Time-consuming:" + (end - start) + "Millisecond");
}
public void doTaskTwo() throws Exception {
System.out.println("Start Task Two");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
System.out.println("Complete Task Two, Time-consuming:" + (end - start) + "Millisecond");
}
public void doTaskThree() throws Exception {
System.out.println("Start Task Three");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
System.out.println("Complete Task Three, Time-consuming:" + (end - start) + "Millisecond");
}
}
In the unit test case, the Task object is injected, and three functions, doTaskOne, doTaskTwo, and doTaskThree, are executed in the test case.
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
public class ApplicationTests {
@Autowired
private Task task;
@Test
public void test() throws Exception {
task.doTaskOne();
task.doTaskTwo();
task.doTaskThree();
}
}
Executing the unit test, you can see output similar to the following:
Start Task One
Task 1 completed in 4256 milliseconds
Start Task Two
Task 2 completed in 4957 milliseconds
Start Task Three
Task 3 completed in 7173 milliseconds
Task 1, Task 2, Task 3 is finished, in other words, doTaskOne, doTaskTwo, doTaskThree three function order is completed.
Asynchronous call
Although the above synchronous invocation successfully completed three tasks, you can see that the execution time is relatively long. If there is no dependency among the three tasks themselves and they can be executed concurrently, the synchronous invocation is less efficient in execution. Consider the way of asynchronous invocation to execute concurrently.
In SpringBoot, we can simply change the original synchronization function to an asynchronous function by using the @Async annotation, and the Task class to the following pattern:
@Component
public class Task {
@Async
public void doTaskOne() throws Exception {
// Same as above, omit
}
@Async
public void doTaskTwo() throws Exception {
// Same as above, omit
}
@Async
public void doTaskThree() throws Exception {
// Same as above, omit
}
}
In order for the @Async annotation to work, you also need to configure @EnableAsync in the main program of SpringBoot, as follows:
@SpringBootApplication
@EnableAsync
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Unit tests can be repeated at this time, and you may encounter different results, such as:
- No task-related output
- Partial Task Related Output
- Output related to unordered tasks
The reason is that the three functions doTaskOne, doTaskTwo, and doTaskThree are now executed asynchronously. After an asynchronous call, the main program does not care if the three functions have been executed. Since there is nothing else to execute, the program automatically terminates, resulting in incomplete or no output task-related content.
Note: @Async decorated functions should not be defined as static type, so asynchronous calls will not take effect
Call-backs
In order for doTaskOne, doTaskTwo, and doTaskThree to end properly, suppose we need to count how much time it takes for the three tasks to execute concurrently. This requires waiting until all three functions have been mobilized, recording the time, and calculating the results.
So how do we determine if the three asynchronous calls above have been executed? We need to use Future to return the result of an asynchronous call, like modifying the doTaskOne function as follows:
@Async
public Future<String> doTaskOne() throws Exception {
System.out.println("Start Task One");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
System.out.println("Complete Task 1, Time-consuming:" + (end - start) + "Millisecond");
return new AsyncResult<>("Task Completed");
}
After revamping the other two asynchronous functions as above, let's revamp the test case so that the test does something else while waiting for three asynchronous calls to complete.
@Test
public void test() throws Exception {
long start = System.currentTimeMillis();
Future<String> task1 = task.doTaskOne();
Future<String> task2 = task.doTaskTwo();
Future<String> task3 = task.doTaskThree();
while(true) {
if(task1.isDone() && task2.isDone() && task3.isDone()) {
// All three tasks are invoked, exiting the loop waiting
break;
}
Thread.sleep(1000);
}
long end = System.currentTimeMillis();
System.out.println("Tasks completed in total time:" + (end - start) + "Millisecond");
}
See what we've changed:
-
Record the start time at the beginning of the test case
-
Returns a result object of type Future when three asynchronous functions are called
-
After three asynchronous functions have been called, a loop is opened to determine whether all three asynchronous functions have ended based on the Future object returned. If it all ends, it ends the cycle; If it's not all over, wait a second before you decide.
After jumping out of the loop, the total time taken for three tasks to execute concurrently is calculated based on the end-start time.
Perform the above unit tests and you can see the following results:
Start Task One
Start Task Two
Start Task Three
Task 3 completed in 37 milliseconds
Task 2 completed in 3661 milliseconds
Task 1 completed in 7149 milliseconds
Task complete, total time: 8025 MS
You can see that by making asynchronous calls, tasks 1, 2, and 3 are executed concurrently, effectively reducing the total running time of the program.