Mastering Asynchronous Execution in Spring Boot: An In‑Depth Guide
This article provides a comprehensive guide to implementing asynchronous processing in Spring Boot, covering annotation‑based @Async, CompletableFuture patterns, WebAsyncTask, DeferredResult, custom thread pools, Tomcat tuning, component scanning, and switching to Undertow, with complete code examples and configuration details.
1. Asynchronous Execution
Spring Boot supports asynchronous processing through two main approaches: using the @Async annotation (combined with @EnableAsync on a configuration class) and leveraging the JDK 8 CompletableFuture API.
@AllArgsConstructor
public class AskThread implements Runnable {
private CompletableFuture<Integer> re = null;
public void run() {
int myRe = 0;
try {
myRe = re.get() * re.get();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(myRe);
}
public static void main(String[] args) throws InterruptedException {
final CompletableFuture<Integer> future = new CompletableFuture<>();
new Thread(new AskThread(future)).start();
// Simulate a long‑running calculation
Thread.sleep(1000);
// Notify the result
future.complete(60);
}
}The example starts a thread that blocks on re.get() until the result is supplied. A Thread.sleep(1000) simulates a time‑consuming operation, after which the result is completed and the thread proceeds.
public class Calc {
public static Integer calc(Integer para) {
try {
// Simulate a long‑running execution
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return para * para;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
final CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> calc(50))
.thenApply(i -> Integer.toString(i))
.thenApply(str -> "\"" + str + "\"")
.thenAccept(System.out::println);
future.get();
}
}Here CompletableFuture.supplyAsync runs calc(50) in a separate thread, transforms the result, and prints it without blocking the main thread.
2. Using runAsync and Callable
CompletableFuture.runAsync(() -> this.afterBetProcessor(betRequest, betDetailResult, appUser, id));When a controller returns a Callable, Spring MVC hands it to a TaskExecutor. The original request thread releases, while the callable runs in the common ForkJoinPool daemon pool.
@RestController
public class HelloController {
private static final Logger logger = LoggerFactory.getLogger(HelloController.class);
@Autowired
private HelloService hello;
@GetMapping("/helloworld")
public String helloWorldController() {
return hello.sayHello();
}
@GetMapping("/hello")
public Callable<String> helloController() {
logger.info(Thread.currentThread().getName() + " 进入helloController方法");
Callable<String> callable = new Callable<String>() {
@Override
public String call() throws Exception {
logger.info(Thread.currentThread().getName() + " 进入call方法");
String say = hello.sayHello();
logger.info(Thread.currentThread().getName() + " 从helloService方法返回");
return say;
}
};
logger.info(Thread.currentThread().getName() + " 从helloController方法返回");
return callable;
}
}3. WebAsyncTask with Timeout Handling
@RestController
public class HelloController {
private static final Logger logger = LoggerFactory.getLogger(HelloController.class);
@Autowired
private HelloService hello;
@GetMapping("/world")
public WebAsyncTask<String> worldController() {
logger.info(Thread.currentThread().getName() + " 进入helloController方法");
WebAsyncTask<String> webAsyncTask = new WebAsyncTask<>(3000, () -> {
logger.info(Thread.currentThread().getName() + " 进入call方法");
String say = hello.sayHello();
logger.info(Thread.currentThread().getName() + " 从helloService方法返回");
return say;
});
webAsyncTask.onCompletion(() -> logger.info(Thread.currentThread().getName() + " 完成"));
webAsyncTask.onTimeout(() -> {
logger.info(Thread.currentThread().getName() + " onTimeout");
throw new TimeoutException("调用超时");
});
return webAsyncTask;
}
@GetMapping("/exception")
public WebAsyncTask<String> exceptionController() {
Callable<String> callable = () -> {
logger.info(Thread.currentThread().getName() + " 进入call方法");
throw new TimeoutException("调用超时!");
};
return new WebAsyncTask<>(20000, callable);
}
}4. Tuning Embedded Tomcat
@Configuration
public class TomcatConfig {
@Bean
public ConfigurableServletWebServerFactory webServerFactory() {
TomcatServletWebServerFactory tomcatFactory = new TomcatServletWebServerFactory();
tomcatFactory.addConnectorCustomizers(new MyTomcatConnectorCustomizer());
tomcatFactory.setPort(8005);
tomcatFactory.setContextPath("/api-g");
return tomcatFactory;
}
class MyTomcatConnectorCustomizer implements TomcatConnectorCustomizer {
@Override
public void customize(Connector connector) {
Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
// Set max connections and threads
protocol.setMaxConnections(20000);
protocol.setMaxThreads(2000);
protocol.setConnectionTimeout(30000);
}
}
}5. Component Scan vs. @SpringBootApplication
Using @ComponentScan directly can be marginally faster than relying on the default component scan performed by @SpringBootApplication, because it avoids scanning the auto‑configuration packages.
6. Switching the Embedded Container to Undertow
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>Undertow typically offers higher throughput (e.g., 8000 requests/s) compared with Tomcat (≈5000 requests/s) in similar environments.
7. BufferedWriter for Efficient I/O
When writing large amounts of data, wrap the output stream with a BufferedWriter to reduce the number of physical writes.
8. DeferredResult for Long‑Running Requests
@RestController
public class AsyncDeferredController {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final LongTimeTask taskService;
@Autowired
public AsyncDeferredController(LongTimeTask taskService) {
this.taskService = taskService;
}
@GetMapping("/deferred")
public DeferredResult<String> executeSlowTask() {
logger.info(Thread.currentThread().getName() + " 进入executeSlowTask方法");
DeferredResult<String> deferredResult = new DeferredResult<>();
// Submit the long‑running task
taskService.execute(deferredResult);
logger.info(Thread.currentThread().getName() + " 从executeSlowTask方法返回");
// Timeout handling
deferredResult.onTimeout(() -> {
logger.info(Thread.currentThread().getName() + " onTimeout");
deferredResult.setErrorResult("time out!");
});
// Completion handling (both success and timeout)
deferredResult.onCompletion(() -> logger.info(Thread.currentThread().getName() + " onCompletion"));
return deferredResult;
}
}9. AsyncHandlerInterceptor for Intercepting Asynchronous Calls
@Component
public class MyAsyncHandlerInterceptor implements AsyncHandlerInterceptor {
private static final Logger logger = LoggerFactory.getLogger(MyAsyncHandlerInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
logger.info(Thread.currentThread().getName() + " 服务调用完成,返回结果给客户端");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
if (ex != null) {
System.out.println("发生异常:" + ex.getMessage());
}
}
@Override
public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// Replace the response body
String resp = "my name is chhliu!";
response.setContentLength(resp.length());
response.getOutputStream().write(resp.getBytes());
logger.info(Thread.currentThread().getName() + " 进入afterConcurrentHandlingStarted方法");
}
}The article concludes with a brief reference list of the source blogs.
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.
Java Architect Essentials
Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow together.
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.
