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.

Architect's Guide
Architect's Guide
Architect's Guide
8 Java Asynchronous Implementation Patterns for High Performance

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.

Synchronous order flow
Synchronous order flow

In synchronous flow, the SMS send must finish before points are granted. With async, both operations start together.

Asynchronous order flow
Asynchronous order flow

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

Future

represents 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

ListenableFuture

extends 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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaCompletableFutureThreadMessage QueueAsynchronous ProgrammingFutureSpring @AsyncGuava ListenableFuture
Architect's Guide
Written by

Architect's Guide

Dedicated to sharing programmer-architect skills—Java backend, system, microservice, and distributed architectures—to help you become a senior architect.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.