8 Powerful Ways to Implement Asynchronous Execution in Java
This article explains what asynchronous execution is, compares it with synchronous processing, and presents eight practical Java implementations—including raw threads, Future, CompletableFuture, Spring @Async, ApplicationEvent, message queues, Hutool ThreadUtil, and Guava ListenableFuture—complete with code samples and usage tips.
Asynchronous execution is familiar to developers; it can greatly reduce request latency in scenarios such as sending SMS, emails, or performing asynchronous updates.
Eight Asynchronous Implementation Methods
Thread
Future
CompletableFuture
Spring @Async annotation
Spring ApplicationEvent
Message queue
Third‑party frameworks such as Hutool ThreadUtil
Guava asynchronous utilities
What Is Asynchrony?
Consider a typical order flow where sending an SMS and awarding points are independent; synchronous execution forces the second step to wait for the first, while asynchronous processing allows both to run concurrently.
By using asynchronous processing, the two operations can be performed simultaneously.
This demonstrates the simplicity of asynchrony.
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 each 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...");
}
});
}Business logic can be wrapped in Runnable or Callable and submitted to the pool.
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!!!";
}
});
// get blocks until result is available
String result = future.get();
log.info("Future get result: {}", result);
return result;
}
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 receive task completion passively; must call get to obtain the result.
Futures are isolated; chaining requires manual handling, unlike CompletableFuture.
Lacks robust error handling; exceptions must be caught from get.
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. Spring @Async Asynchrony
Custom async thread pool configuration:
@EnableAsync
@Configuration
public class TaskPoolConfig {
@Bean("taskExecutor")
public Executor taskExecutor() {
int processors = Runtime.getRuntime().availableProcessors();
System.out.println("System max threads: " + processors);
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 interface and implementation using @Async:
public interface AsyncService {
MessageResult sendSms(String callPrefix, String mobile, String actionType, String content);
MessageResult sendEmail(String email, String subject, String content);
}
@Service
public class AsyncServiceImpl implements AsyncService {
@Autowired
private IMessageHandler messageHandler;
@Async("taskExecutor")
public MessageResult 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);
}
return null;
}
@Async("taskExecutor")
public MessageResult 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);
}
return null;
}
}Using a custom thread pool with @Async is recommended over the default implementation.
5. Spring ApplicationEvent Asynchrony
Define an event class:
public class AsyncSendEmailEvent extends ApplicationEvent {
private String email;
private String subject;
private String content;
private String targetUserId;
// getters and setters omitted
}Event handler:
@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(), event.getTargetUserId());
}
}Combine with Spring Retry for compensation when errors occur.
6. Message Queue Asynchrony
Producer example:
@Component
public class CallbackProducer {
@Autowired
AmqpTemplate amqpTemplate;
public void sendCallbackMessage(CallbackDTO callbackDTO, long delayTimes) {
log.info("Producer sending message, 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:
@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;
}
try {
CallbackDTO callbackDTO = JsonMapper.getInstance().fromJson(json, CallbackDTO.class);
globalUserService.execute(callbackDTO);
channel.basicAck((Long) headers.get(AmqpHeaders.DELIVERY_TAG), false);
} catch (Exception e) {
log.error("Callback failure -> {}", e);
}
}
}7. Hutool ThreadUtil Asynchrony
public class ThreadUtils {
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
ThreadUtil.execAsync(() -> {
ThreadLocalRandom random = ThreadLocalRandom.current();
int number = random.nextInt(20) + 1;
System.out.println(number);
});
log.info("Current thread index: " + i);
}
log.info("task finish!");
}
}8. Guava Asynchrony
Guava's ListenableFuture extends JDK Future with a listener mechanism.
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();
}
});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.
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
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.
