Last Updated on July 30, 2023 by KnownSense
Asynchronous programming is a programming paradigm that allows tasks to run independently of the main program flow. Instead of waiting for each task to complete before moving on to the next one, asynchronous programming enables the program to continue executing while certain tasks are being processed in the background.
In asynchronous programming, tasks are typically executed concurrently or in a non-blocking manner. This means that when a task encounters a long-running operation, such as waiting for data from a remote server or reading/writing files, it doesn’t halt the entire program’s execution. Instead, the program continues to perform other tasks or respond to user interactions, making the application more responsive and efficient.
Why Asynchronous Code?
- Responsiveness: Asynchronous programming ensures that the program remains responsive even when dealing with time-consuming tasks. It allows other parts of the program to continue executing while certain tasks are running in the background.
- Efficiency: Asynchronous operations make better use of resources by avoiding blocking operations. Instead of waiting for one task to complete before starting the next, multiple tasks can run concurrently, utilizing CPU and I/O resources more efficiently.
- Concurrency and Scalability: Asynchronous programming enables concurrent execution of tasks, allowing applications to handle multiple operations simultaneously. This improves overall performance and scalability, especially in high-load scenarios.
- I/O-Bound Operations: For operations involving input/output tasks (e.g., reading from a file or making network requests), asynchronous programming prevents unnecessary waiting and allows the program to keep processing other tasks.
- Parallel Processing: Asynchronous programming allows for parallel processing, where tasks can be distributed across multiple cores or machines, increasing overall throughput.
- Event-Driven Architecture: Asynchronous programming forms the basis for event-driven architectures, where components respond to events triggered by external or internal sources. This enables building reactive and event-based systems.
- Handling Long-Running Tasks: Asynchronous programming is beneficial for tasks that take a long time to complete, such as data processing or rendering. It ensures that other parts of the application can continue running smoothly.
Synchronous Vs Asynchronous Code
| Synchronous Code | Asynchronous Code | |
| Execution Flow | Sequential | Non-blocking |
| Waiting | Blocks the thread | Doesn’t block the thread |
| Responsiveness | Can be slow if tasks take time | Responsive, other tasks can run concurrently |
| Error Handling | Exception handling is straightforward | Requires additional handling for errors and exceptions |
| Threading | Single-threaded | Can be multi-threaded or utilize thread pools |
| Complexity | Typically simpler to write and understand | Can be more complex, especially with callbacks or nested async tasks |
| Use Cases | Suitable for tasks with low latency and quick execution | Ideal for tasks with potential delays or I/O operations |
Different ways for asynchronous programming
There are several ways to achieve asynchronous programming to handle long-running tasks or improve application responsiveness. Here are some common approaches:
1. @Async with ThreadPoolTaskExecutor
Spring Boot provides support for asynchronous processing using the @Async annotation along with ThreadPoolTaskExecutor. By annotating a method with @Async, Spring will execute that method asynchronously in a separate thread from the ThreadPoolTaskExecutor.
To use the @Async annotation in a Spring Boot application, follow these steps:
Enable Async Support: In your Spring Boot configuration class or main application class, enable asynchronous support by adding the @EnableAsync annotation. This enables Spring to create the necessary infrastructure for handling asynchronous method calls.
@SpringBootApplication
@EnableAsync
public class YourSpringBootApplication {
public static void main(String[] args) {
SpringApplication.run(YourSpringBootApplication.class, args);
}
}
Create a Method to Be Executed Asynchronously: In any Spring component (service, controller, etc.), define a public method that you want to execute asynchronously. Add the @Async annotation to this method. This annotation doesn’t work on private methods.
@Service
public class YourService {
@Async
public void performAsyncTask() {
// Your asynchronous task logic goes here
// This method will be executed asynchronously in a separate thread
}
}
2. ExecutorService:
Java’s ExecutorService framework provides a higher-level abstraction for managing threads and executing asynchronous tasks. It allows you to submit Runnable or Callable tasks for execution and handles thread pooling and resource management.
public class AsyncWithExecutorService {
record Quotation(String server, int amount){}
public static void main(String args[]) {
run();
}
public static void run() {
Random random= new Random();
Callable<Quotation> q1= ()->{
Thread.sleep(1000);
return new Quotation("server-a", random.nextInt());
};
Callable<Quotation> q2= ()->{
Thread.sleep(1000);
return new Quotation("server-b", random.nextInt());
};
Callable<Quotation> q3= ()->{
Thread.sleep(1000);
return new Quotation("server-b", random.nextInt());
};
List<Callable<Quotation>> quotationList = Arrays.asList(q1,q2,q3);
usingExecutor(quotationList);
}
private static void usingExecutor(List<Callable<Quotation>> quotationList) {
ExecutorService executor = Executors.newFixedThreadPool(4);
List<Future<Quotation>> futures = new ArrayList<>();
List<Quotation> quotations =new ArrayList<>();
Instant beginNow = Instant.now();
for (Callable<Quotation> task: quotationList){
Future<Quotation> future= executor.submit(task);
futures.add(future);
}
futures.forEach(t-> {
try {
quotations.add(t.get());
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
});
quotations.stream().min(Comparator.comparing(Quotation::amount)).orElseThrow();
Instant endNow= Instant.now();
Duration duration = Duration.between(beginNow,endNow);
System.out.println("time taken to find best quote usingExecutor: "+ duration.toMillis()+"ms");
}
3. CompletableFuture:
Introduced in Java 8, CompletableFuture provides a powerful way to perform asynchronous operations and handle their results using chained callbacks. It allows for more readable and concise code compared to traditional Future and Callable.
public class CompletableFutureExample {
public static void main(String args[]) {
run();
}
public static void run() {
Supplier<Double> task1= ()->{
System.out.println("task1 completed");
return Math.random();
};
Supplier<Double> task2= ()->{
System.out.println("task2 completed");
return Math.random();
};
Supplier<Double> task3= ()->{
System.out.println("task3 completed");
return Math.random();
};
List<Supplier<Double>> taskList = Arrays.asList(task1,task2,task3);
List<CompletableFuture<Double>> futureList = new ArrayList<>();
List<Double> ans = new ArrayList<>();
for (Supplier<Double> task: taskList){
CompletableFuture<Double> future= CompletableFuture.supplyAsync(task);
futureList.add(future);
}
futureList.forEach(future->{
try {
ans.add(future.get());
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}
}
4. Java Concurrency Utilities:
Java provides various concurrency utilities like CountDownLatch, CyclicBarrier, and Semaphore, which can be used to coordinate and synchronize asynchronous tasks.
5. WebFlux with Reactor:
Spring WebFlux is part of the Spring Framework’s reactive stack and provides support for reactive programming using Project Reactor. You can use WebFlux to handle asynchronous and non-blocking I/O operations, which is beneficial for building responsive, scalable applications.
6. Asynchronous REST Endpoints:
By returning DeferredResult, CompletableFuture, or using WebFlux, you can make specific REST endpoints in your Spring Boot application asynchronous, which can help improve scalability and responsiveness.
7. Asynchronous Database Access:
For database operations, you can use Spring Data’s reactive repositories (supported by NoSQL databases) or leverage asynchronous database drivers to perform non-blocking I/O database access.
8. @Scheduled Annotation:
Spring Boot allows scheduling tasks using the @Scheduled annotation, which can be used to execute methods periodically or at fixed intervals. While not strictly asynchronous, it can help with scheduling and automating tasks asynchronously.
9. ListenableFuture
Guava’s ListenableFuture is similar to Java’s Future, but it allows you to add listeners to be executed when the computation is complete. This makes it easier to handle asynchronous results and perform actions once the task is done.
Conclusion
In conclusion, we have explored asynchronous programming and its significance in modern software development. Asynchronous code allows tasks to run independently in the background, enabling responsiveness and efficient resource utilization. By freeing the main program flow from waiting for long-running tasks, applications can remain interactive and handle multiple operations concurrently. We compared synchronous and asynchronous code, highlighting the benefits of asynchronous programming. Furthermore, we explored various ways to achieve asynchronous programming in a Spring Boot and java application.