Master Asynchronous Java: Threads, Futures, CompletableFuture & Spring @Async
This article introduces asynchronous programming concepts in Java, explaining the shift from synchronous to asynchronous execution, and demonstrates practical implementations using raw threads, ThreadPoolExecutor, Future, FutureTask, CompletableFuture, and Spring's @Async annotation, along with event handling and message queue integration for high‑throughput systems.
Hello, I'm Su San. Many friends asked for a summary of asynchronous programming, so here's a brief overview.
Synchronous Programming
Early systems were synchronous and easy to understand. Example:
Why Asynchronous?
When creating an e‑commerce order, the business logic can be long, increasing overall response time. Developers separate non‑core tasks from the main flow, leading to the concept of asynchronous programming.
Asynchronous programming allows programs to run concurrently, letting long‑running methods execute without blocking the current flow.
Core Idea
Use multithreading to turn serial operations into parallel ones, reducing thread waiting and improving throughput and latency.
1. Thread
Extending Thread is the simplest way to create an asynchronous thread.
Steps: create a Thread subclass (or anonymous class), instantiate it, and start it.
public class AsyncThread extends Thread {
@Override
public void run() {
System.out.println("Current thread name:" + this.getName()
+ ", executing thread name:" + Thread.currentThread().getName() + "-hello");
}
}
public static void main(String[] args) {
// simulate business flow
AsyncThread asyncThread = new AsyncThread();
asyncThread.start();
}Creating a new Thread for each task wastes resources; a thread pool is preferred.
@Bean(name = "executorService")
public ExecutorService downloadExecutorService() {
return new ThreadPoolExecutor(20, 40, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(2000),
new ThreadFactoryBuilder().setNameFormat("defaultExecutorService-%d").build(),
(r, executor) -> log.error("defaultExecutor pool is full! "));
}Encapsulate business logic in Runnable or Callable and submit to the thread pool.
2. Future
When a task needs to return a result, Java 1.5 introduced Callable and Future.
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}Key methods:
cancel(): attempts to cancel the task.
isCancelled(): true if the task was cancelled before completion.
isDone(): true if the task has finished.
get(): blocks until the result is available.
get(timeout, unit): returns null if timeout expires.
Example:
public class CallableAndFuture {
public static ExecutorService executorService = new ThreadPoolExecutor(4, 40,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(1024),
new ThreadFactoryBuilder().setNameFormat("demo-pool-%d").build(),
new ThreadPoolExecutor.AbortPolicy());
static class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "Asynchronous processing, Callable returns result";
}
}
public static void main(String[] args) {
Future<String> future = executorService.submit(new MyCallable());
try {
System.out.println(future.get());
} catch (Exception e) {
// handle
} finally {
executorService.shutdown();
}
}
}Future represents a pending result; get() blocks until the task completes.
3. FutureTask
FutureTaskimplements RunnableFuture, combining Runnable and Future, allowing it to be submitted to a thread pool and to retrieve results.
public FutureTask(Callable<V> callable)
public FutureTask(Runnable runnable, V result)Commonly used to wrap Callable or Runnable for execution in a thread pool.
ExecutorService executor = Executors.newCachedThreadPool();
FutureTask<Integer> futureTask = new FutureTask<>(() -> {
System.out.println("Child thread starts calculation:");
int sum = 0;
for (int i = 1; i <= 100; i++) sum += i;
return sum;
});
executor.submit(futureTask);
System.out.println("Task result sum: " + futureTask.get());
executor.shutdown();Callable produces a result; Future retrieves the result.
4. CompletableFuture
JDK 8 introduced CompletableFuture, a functional asynchronous API that avoids blocking get() by using callbacks.
Advantages:
Automatically invokes a callback when the async task finishes.
Automatically invokes a callback on error.
Main thread sets callbacks and does not need to manage execution.
Example (tea‑making analogy):
// Task 1: wash kettle → boil water
CompletableFuture<Void> f1 = CompletableFuture.runAsync(() -> {
System.out.println("T1: wash kettle...");
sleep(1, TimeUnit.SECONDS);
System.out.println("T1: boil water...");
sleep(15, TimeUnit.SECONDS);
});
// Task 2: wash teapot → wash teacup → get tea leaves
CompletableFuture<String> f2 = CompletableFuture.supplyAsync(() -> {
System.out.println("T2: wash teapot...");
sleep(1, TimeUnit.SECONDS);
System.out.println("T2: wash teacup...");
sleep(2, TimeUnit.SECONDS);
System.out.println("T2: get tea leaves...");
sleep(1, TimeUnit.SECONDS);
return "Longjing";
});
// Task 3: after f1 and f2, make tea
CompletableFuture<String> f3 = f1.thenCombine(f2, (__, tf) -> {
System.out.println("T1: got tea leaves: " + tf);
System.out.println("T1: making tea...");
return "Serve tea: " + tf;
});
System.out.println(f3.join());CompletableFuture offers about 50 methods for serial, parallel, combination, and error handling.
5. Spring @Async
Spring Boot provides annotation‑driven asynchronous execution.
@SpringBootApplication
@EnableAsync
public class StartApplication {
public static void main(String[] args) {
SpringApplication.run(StartApplication.class, args);
}
}Define a custom thread pool:
@Configuration
public class ThreadPoolConfiguration {
@Bean(name = "defaultThreadPoolExecutor", destroyMethod = "shutdown")
public ThreadPoolExecutor systemCheckPoolExecutorService() {
return new ThreadPoolExecutor(3, 10, 60, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(10000),
new ThreadFactoryBuilder().setNameFormat("default-executor-%d").build(),
(r, executor) -> log.error("system pool is full! "));
}
}Mark methods with @Async to run them asynchronously using the defined pool.
@Service
public class AsyncServiceImpl implements AsyncService {
@Async("defaultThreadPoolExecutor")
public Boolean execute(Integer num) {
System.out.println("Thread: " + Thread.currentThread().getName() + " , task: " + num);
return true;
}
}Usage steps: enable @EnableAsync, annotate target methods with @Async, and ensure the bean is managed by Spring.
6. Spring ApplicationEvent
Spring's event mechanism decouples components via the observer pattern.
Publish an event with ApplicationContext.publishEvent(new OrderEvent(orderModel)). Listeners implement ApplicationListener<OrderEvent> to handle events.
public class OrderEvent extends AbstractGenericEvent<OrderModel> {
public OrderEvent(OrderModel source) {
super(source);
}
} @Component
public class OrderEventListener implements ApplicationListener<OrderEvent> {
@Override
public void onApplicationEvent(OrderEvent event) {
System.out.println("[OrderEventListener] handling! " + JSON.toJSONString(event.getSource()));
}
}By default the event dispatch is synchronous; to make it asynchronous, configure a SimpleApplicationEventMulticaster with a TaskExecutor bean.
@Component
public class SpringConfiguration {
@Bean
public SimpleApplicationEventMulticaster applicationEventMulticaster(
@Qualifier("defaultThreadPoolExecutor") ThreadPoolExecutor defaultThreadPoolExecutor) {
SimpleApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster();
multicaster.setTaskExecutor(defaultThreadPoolExecutor);
return multicaster;
}
}7. Message Queues
Message queues are a typical asynchronous architecture component, providing high throughput and low latency. They consist of producers, the queue, and consumers, supporting point‑to‑point and publish‑subscribe modes. Common implementations include RabbitMQ, Kafka, RocketMQ, ActiveMQ, and Pulsar.
Using a message queue allows efficient asynchronous processing in distributed systems.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Su San Talks Tech
Su San, former staff at several leading tech companies, is a top creator on Juejin and a premium creator on CSDN, and runs the free coding practice site www.susan.net.cn.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
