8 Powerful Ways to Implement Asynchronous Programming in Java
This article explains why asynchronous execution shortens request latency and presents eight practical Java techniques—including raw threads, Future, CompletableFuture, Spring @Async, ApplicationEvent, message queues, ThreadUtil, and Guava ListenableFuture—complete with code examples and usage tips.
Asynchronous execution is essential for developers to shorten request latency, allowing independent tasks such as sending SMS, emails, or updating data to run concurrently.
Thread
Future
CompletableFuture
Spring annotation @Async
Spring ApplicationEvent
Message queue
Third‑party async frameworks (e.g., Hutool ThreadUtil)
Guava ListenableFuture
What Is Asynchrony?
In a synchronous flow, the method that sends an SMS must wait for the subsequent "grant points" operation to finish, even though the two actions are unrelated. By using asynchronous calls, both operations can proceed in parallel.
The following sections demonstrate each implementation.
4.1 Thread Asynchrony
public class AsyncThread extends Thread {
@Override
public void run() {
System.out.println("Current thread name:" + Thread.currentThread().getName() + " Send email success!");
}
public static void main(String[] args) {
AsyncThread asyncThread = new AsyncThread();
asyncThread.run();
}
}Creating a new Thread for every task wastes resources; a thread pool is preferred.
private ExecutorService executorService = Executors.newCachedThreadPool();
public void fun() {
executorService.submit(new Runnable() {
@Override
public void run() {
log.info("Executing business logic...");
}
});
}4.2 Future Asynchrony
@Slf4j
public class FutureManager {
public String execute() throws Exception {
ExecutorService executor = Executors.newFixedThreadPool(1);
Future<String> future = executor.submit(new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println(" --- task start --- ");
Thread.sleep(3000);
System.out.println(" --- task finish ---");
return "this is future execute final result!!!";
}
});
String result = future.get(); // blocks if result not ready
log.info("Future get result: {}", result);
return result;
}
@SneakyThrows
public static void main(String[] args) {
new FutureManager().execute();
}
}Output:
--- task start ---
--- task finish ---
Future get result: this is future execute final result!!!Future Limitations
Cannot receive task completion notifications passively.
Futures are isolated; they cannot form pipelines.
Lacks robust error‑handling; exceptions must be caught from get().
4.3 CompletableFuture Asynchrony
public class CompletableFutureCompose {
@SneakyThrows
public static void thenRunAsync() {
CompletableFuture<Integer> cf1 = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread() + " cf1 do something....");
return 1;
});
CompletableFuture<Void> cf2 = cf1.thenRunAsync(() -> {
System.out.println(Thread.currentThread() + " cf2 do something...");
});
System.out.println("cf1 result -> " + cf1.get());
System.out.println("cf2 result -> " + cf2.get());
}
public static void main(String[] args) {
thenRunAsync();
}
}CompletableFuture uses an internal ForkJoinPool and can be configured with a custom executor.
4.4 Spring @Async
@Configuration
public class TaskPoolConfig {
@Bean("taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(16);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(99999);
executor.setKeepAliveSeconds(60);
executor.setThreadNamePrefix("asyncServiceExecutor -");
executor.setAwaitTerminationSeconds(60);
executor.setWaitForTasksToCompleteOnShutdown(true);
return executor;
}
} public interface AsyncService {
MessageResult sendSms(String callPrefix, String mobile, String actionType, String content);
MessageResult sendEmail(String email, String subject, String content);
}
@Slf4j
@Service
public class AsyncServiceImpl implements AsyncService {
@Autowired
private IMessageHandler messageHandler;
@Async("taskExecutor")
public void sendSms(String callPrefix, String mobile, String actionType, String content) {
try {
Thread.sleep(1000);
messageHandler.sendSms(callPrefix, mobile, actionType, content);
} catch (Exception e) {
log.error("SMS send exception -> ", e);
}
}
@Async("taskExecutor")
public void sendEmail(String email, String subject, String content) {
try {
Thread.sleep(1000);
messageHandler.sendEmail(email, subject, content);
} catch (Exception e) {
log.error("Email send exception -> ", e);
}
}
}Using a custom thread pool with @Async is recommended over the default executor.
4.5 Spring ApplicationEvent Asynchrony
public class AsyncSendEmailEvent extends ApplicationEvent {
private String email;
private String subject;
private String content;
private String targetUserId;
} @Component
public class AsyncSendEmailEventHandler implements ApplicationListener<AsyncSendEmailEvent> {
@Autowired
private IMessageHandler messageHandler;
@Async("taskExecutor")
@Override
public void onApplicationEvent(AsyncSendEmailEvent event) {
if (event == null) return;
messageHandler.sendEmail(event.getEmail(), event.getSubject(), event.getContent());
}
}Combine with Spring Retry for fault tolerance.
4.6 Message Queue Asynchrony
@Component
public class CallbackProducer {
@Autowired
AmqpTemplate amqpTemplate;
public void sendCallbackMessage(CallbackDTO dto, long delay) {
amqpTemplate.convertAndSend(CallbackQueueEnum.QUEUE_GENSEE_CALLBACK.getExchange(),
CallbackQueueEnum.QUEUE_GENSEE_CALLBACK.getRoutingKey(),
JsonMapper.getInstance().toJson(dto),
message -> {
message.getMessageProperties().setHeader("x-delay", delay);
message.getMessageProperties().setCorrelationId(dto.getSdkId());
return message;
});
}
} @Component
@RabbitListener(queues = "message.callback", containerFactory = "rabbitListenerContainerFactory")
public class CallbackConsumer {
@Autowired
private IGlobalUserService globalUserService;
@RabbitHandler
public void handle(String json, Channel channel, @Headers Map<String, Object> headers) throws Exception {
if (headers.get("error") != null) {
channel.basicNack((Long) headers.get(AmqpHeaders.DELIVERY_TAG), false, true);
return;
}
CallbackDTO dto = JsonMapper.getInstance().fromJson(json, CallbackDTO.class);
globalUserService.execute(dto);
channel.basicAck((Long) headers.get(AmqpHeaders.DELIVERY_TAG), false);
}
}4.7 Hutool ThreadUtil
public class ThreadUtils {
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
ThreadUtil.execAsync(() -> {
int number = ThreadLocalRandom.current().nextInt(20) + 1;
System.out.println(number);
});
log.info("Current thread index: " + i);
}
log.info("task finish!");
}
}4.8 Guava ListenableFuture
ListeningExecutorService executorService = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
ListenableFuture<Integer> listenableFuture = executorService.submit(() -> {
log.info("callable execute...");
TimeUnit.SECONDS.sleep(1);
return 1;
});
Futures.addCallback(listenableFuture, new FutureCallback<Integer>() {
@Override
public void onSuccess(Integer result) {
System.out.println("Get listenable future's result with callback " + result);
}
@Override
public void onFailure(Throwable t) {
t.printStackTrace();
}
});The article concludes that these eight methods cover most common scenarios for implementing asynchronous processing in Java.
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.
Architect's Guide
Dedicated to sharing programmer-architect skills—Java backend, system, microservice, and distributed architectures—to help you become a senior architect.
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.
