8 Powerful Ways to Implement Asynchronous Execution in Java
Understanding asynchronous execution in Java can dramatically reduce latency for tasks such as sending SMS, emails, or updating data, and this article walks through eight practical implementations—from raw Threads and Futures to Spring @Async, ApplicationEvent, message queues, ThreadUtil, and Guava ListenableFuture—complete with code samples and best‑practice tips.
Asynchronous execution is common in development to reduce request latency, for example sending SMS, emails, or performing async updates.
Eight ways to implement async
Thread
Future
CompletableFuture
Spring @Async
Spring ApplicationEvent
Message Queue
Third‑party frameworks such as Hutool ThreadUtil
Guava async
What is async?
Consider an order‑placement scenario where sending an SMS and awarding points are independent. In synchronous code the second step must wait for the first, while asynchronous execution allows both to run concurrently.
Using async, the two operations can be performed simultaneously.
1. Thread async
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 each time 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("执行业务逻辑...");
}
});
}2. Future async
@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!!!";
}
});
// get blocks if result is needed
String result = future.get();
log.info("Future get result: {}", result);
return result;
}
@SneakyThrows
public static void main(String[] args) {
FutureManager manager = new FutureManager();
manager.execute();
}
}Output:
--- task start ---
--- task finish ---
Future get result: this is future execute final result!!!Shortcomings of Future
Cannot passively receive task result; must call get() explicitly.
Futures are isolated; no built‑in pipeline support.
Lacks robust error handling; exceptions must be caught from get().
3. CompletableFuture async
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结果->" + cf1.get());
System.out.println("cf2结果->" + cf2.get());
}
public static void main(String[] args) {
thenRunAsync();
}
}CompletableFuture uses ForkJoinPool internally; a custom pool can be supplied when needed.
4. Spring @Async
Define a custom thread pool:
@EnableAsync
@Configuration
public class TaskPoolConfig {
@Bean("taskExecutor")
public Executor taskExecutor() {
int i = Runtime.getRuntime().availableProcessors();
System.out.println("系统最大线程数 : " + i);
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;
}
}Service implementation using @Async:
@Service
public class AsyncServiceImpl implements AsyncService {
@Autowired
private IMessageHandler mesageHandler;
@Override
@Async("taskExecutor")
public MessageResult sendSms(String callPrefix, String mobile, String actionType, String content) {
try {
Thread.sleep(1000);
mesageHandler.sendSms(callPrefix, mobile, actionType, content);
} catch (Exception e) {
log.error("发送短信异常 -> ", e);
}
}
@Override
@Async("taskExecutor")
public MessageResult sendEmail(String email, String subject, String content) {
try {
Thread.sleep(1000);
mesageHandler.sendsendEmail(email, subject, content);
} catch (Exception e) {
log.error("发送email异常 -> ", e);
}
}
}Custom @Async is preferred over the default thread pool.
5. Spring ApplicationEvent async
Define an event class and a listener that processes it asynchronously.
public class AsyncSendEmailEvent extends ApplicationEvent {
private String email;
private String subject;
private String content;
private String targetUserId;
// getters and setters
} @Component
public class AsyncSendEmailEventHandler implements ApplicationListener<AsyncSendEmailEvent> {
@Autowired
private IMessageHandler mesageHandler;
@Async("taskExecutor")
@Override
public void onApplicationEvent(AsyncSendEmailEvent event) {
if (event == null) return;
mesageHandler.sendsendEmailSms(event.getEmail(), event.getSubject(),
event.getContent(), event.getTargetUserId());
}
}Combine with Spring Retry for error compensation when needed.
6. Message Queue
Producer example using RabbitTemplate with delayed messages:
@Component
public class CallbackProducer {
@Autowired
AmqpTemplate amqpTemplate;
public void sendCallbackMessage(CallbackDTO callbackDTO, final long delayTimes) {
log.info("生产者发送消息,callbackDTO,{}", callbackDTO);
amqpTemplate.convertAndSend(CallbackQueueEnum.QUEUE_GENSEE_CALLBACK.getExchange(),
CallbackQueueEnum.QUEUE_GENSEE_CALLBACK.getRoutingKey(),
JsonMapper.getInstance().toJson(callbackDTO),
message -> {
message.getMessageProperties().setHeader("x-delay", delayTimes);
message.getMessageProperties().setCorrelationId(callbackDTO.getSdkId());
return message;
});
}
}Consumer example with manual acknowledgment:
@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> map) throws Exception {
if (map.get("error") != null) {
channel.basicNack((Long) map.get(AmqpHeaders.DELIVERY_TAG), false, true);
return;
}
try {
CallbackDTO callbackDTO = JsonMapper.getInstance().fromJson(json, CallbackDTO.class);
globalUserService.execute(callbackDTO);
channel.basicAck((Long) map.get(AmqpHeaders.DELIVERY_TAG), false);
} catch (Exception e) {
log.error("回调失败 -> {}", e);
}
}
}7. ThreadUtil async utility
public class ThreadUtils {
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
ThreadUtil.execAsync(() -> {
ThreadLocalRandom threadLocalRandom = ThreadLocalRandom.current();
int number = threadLocalRandom.nextInt(20) + 1;
System.out.println(number);
});
log.info("当前第:" + i + "个线程");
}
log.info("task finish!");
}
}8. Guava async
Guava's ListenableFuture extends JDK Future and allows listeners without polling.
ListeningExecutorService executorService = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
final 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();
}
});Java High-Performance Architecture
Sharing Java development articles and resources, including SSM architecture and the Spring ecosystem (Spring Boot, Spring Cloud, MyBatis, Dubbo, Docker), Zookeeper, Redis, architecture design, microservices, message queues, Git, etc.
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.
