Home » Asynchronous programming in java

Asynchronous programming in java

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?

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. Parallel Processing: Asynchronous programming allows for parallel processing, where tasks can be distributed across multiple cores or machines, increasing overall throughput.
  6. 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.
  7. 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 CodeAsynchronous Code
Execution FlowSequentialNon-blocking
WaitingBlocks the threadDoesn’t block the thread
ResponsivenessCan be slow if tasks take timeResponsive, other tasks can run concurrently
Error HandlingException handling is straightforwardRequires additional handling for errors and exceptions
ThreadingSingle-threadedCan be multi-threaded or utilize thread pools
ComplexityTypically simpler to write and understandCan be more complex, especially with callbacks or nested async tasks
Use CasesSuitable for tasks with low latency and quick executionIdeal 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.

Scroll to Top