8 Java Asynchronous Implementation Patterns for High Performance
This article explores eight ways to implement asynchronous programming in Java, including Thread, Future, CompletableFuture, Spring @Async, ApplicationEvent, message queues, Hutool ThreadUtil, and Guava ListenableFuture, with code examples and trade-offs for each approach.
What Is Asynchronous Execution?
Asynchronous execution allows independent tasks to run concurrently, reducing overall request latency. A typical example is a user placing an order where sending an SMS and granting loyalty points have no dependency; they can execute in parallel instead of sequentially.
In synchronous flow, the SMS send must finish before points are granted. With async, both operations start together.
1. Thread
Extend Thread and override run(). Creating a new thread per task wastes resources; use a thread pool instead.
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();
}
}Thread pool example with ExecutorService:
private ExecutorService executorService = Executors.newCachedThreadPool();
public void fun() {
executorService.submit(new Runnable() {
@Override
public void run() {
log.info("执行业务逻辑...");
}
});
}Wrap business logic in Runnable or Callable and submit to the pool.
2. Future
Futurerepresents a result of an asynchronous computation. Submit a Callable to an ExecutorService and call get() to retrieve the result (blocks until done).
@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!!!";
}
});
// Blocks until result is available
String result = future.get();
log.info("Future get result: {}", result);
return result;
}
}Output:
--- task start ---
--- task finish ---
Future get result: this is future execute final result!!!Limitations of Future
Cannot passively receive completion notifications; must actively call get().
Futures are isolated; chaining multiple async tasks requires manual wiring.
No built-in error handling; exceptions surface only when get() is called.
3. CompletableFuture
Introduced in Java 8, CompletableFuture supports chaining, composition, and exception handling. It uses ForkJoinPool by default but allows custom executors.
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();
}
}4. Spring @Async
Enable async with @EnableAsync and define a custom ThreadPoolTaskExecutor bean. Annotate service methods with @Async("taskExecutor") to run them in that 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:
@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);
}
return null;
}
@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);
}
return null;
}
}Recommendation: always use a custom thread pool rather than the default.
5. Spring ApplicationEvent
Define an event class extending ApplicationEvent, then a listener implementing ApplicationListener with @Async on the handler method.
public class AsyncSendEmailEvent extends ApplicationEvent {
private String email;
private String subject;
private String content;
private String targetUserId;
// getters/setters omitted
} @Component
public class AsyncSendEmailEventHandler implements ApplicationListener<AsyncSendEmailEvent> {
@Autowired
private IMessageHandler mesageHandler;
@Async("taskExecutor")
@Override
public void onApplicationEvent(AsyncSendEmailEvent event) {
if (event == null) return;
String email = event.getEmail();
String subject = event.getSubject();
String content = event.getContent();
String targetUserId = event.getTargetUserId();
mesageHandler.sendsendEmailSms(email, subject, content, targetUserId);
}
}Combine with Spring Retry for compensation on failures.
6. Message Queue (RabbitMQ Example)
Producer sends a message with a delay header; consumer processes with manual acknowledgment.
@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),
new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setHeader("x-delay", delayTimes);
message.getMessageProperties().setCorrelationId(callbackDTO.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> 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. Hutool ThreadUtil
Hutool's ThreadUtil.execAsync() simplifies fire-and-forget tasks.
@Slf4j
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 ListenableFuture
ListenableFutureextends Future with an addListener(Runnable, Executor) method, enabling callbacks on completion without polling.
Create a ListeningExecutorService via MoreExecutors.listeningDecorator(), submit a Callable, then attach a FutureCallback.
ListeningExecutorService executorService = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
final ListenableFuture<Integer> listenableFuture = executorService.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
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();
}
});This avoids blocking get() and reduces concurrency complexity.
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.
