Concurrency in real systems question 125 of 180 mid
Which thread runs a Spring @Async method, and what does it lose?
- The caller's thread, asynchronously
- Correct: A thread from the configured task executor, which loses the transaction, the security context and the request scope unless they are propagated
- A new thread per call, always
- The common ForkJoinPool
@Async returns immediately and runs the method on the TaskExecutor bean. Three things do not travel
with it, and all three are ThreadLocal-based: the transaction bound to the calling thread, the
SecurityContext, and anything request-scoped, including the MDC values your logs correlate on.
The defaults are the part to warn about, and there are three of them. Plain Spring Framework with no
executor bean falls back to SimpleAsyncTaskExecutor, which starts a thread per call and never
refuses one, so a burst is an outage. Spring Boot auto-configures a ThreadPoolTaskExecutor instead,
but its defaults are eight core threads and an unbounded queue, which replaces the thread explosion
with a silent backlog. Since Boot 3.2, spring.threads.virtual.enabled=true makes it a virtual thread
per task.
So define your own: a ThreadPoolTaskExecutor with a bounded queue and a rejection policy you chose,
plus a TaskDecorator or DelegatingSecurityContextAsyncTaskExecutor if the context has to travel.
Two more traps worth naming: @Async on a method called from inside the same bean does nothing,
because the proxy is bypassed, and a void @Async method throws into the void unless you return
CompletableFuture or register an AsyncUncaughtExceptionHandler.
Quiz yourself on Concurrency in real systems Suggest a correction