Understanding Spring MVC Controller Singleton Scope and Thread Safety
The article explains that Spring MVC Controllers are singleton beans by default, discusses the thread‑safety risks of using instance variables in such Controllers, and presents several solutions—including prototype scope, ThreadLocal, and AtomicInteger—to ensure safe concurrent handling of requests.
In Spring MVC, a @Controller is a singleton bean by default, which means all incoming HTTP requests share the same instance. While the singleton pattern can reduce memory usage, it also introduces thread‑safety problems when instance variables are accessed concurrently.
Tomcat creates a dedicated thread for each request; if more requests arrive than available threads, Tomcat creates additional threads up to maxThreads and then queues excess connections, eventually refusing them when limits are exceeded (source: Tomcat documentation).
Because each servlet in Tomcat is a singleton, Spring MVC Controllers inherit this behavior. The default singleton scope allows high concurrency but makes any mutable state in the Controller shared across threads.
Using an instance variable such as private int num inside a Controller leads to race conditions: multiple threads may read and modify num simultaneously, breaking idempotency and causing inconsistent results.
@Controller
public class TestController {
private int num = 0;
@RequestMapping("/addNum")
public void addNum() {
System.out.println(++num);
}
}When the endpoint /addNum is called twice, the first request prints 1 and the second prints 2, demonstrating that the shared variable is modified across requests.
To make Controllers thread‑safe, the article suggests several approaches:
Avoid defining mutable instance variables in Controllers.
If a variable is required, change the bean scope to prototype: @Scope("prototype"), which creates a new instance per request.
Use ThreadLocal to give each thread its own copy of a variable.
Prefer AtomicInteger or other atomic types for safe concurrent increments.
@Controller
@Scope(value="prototype")
public class TestController {
private int num = 0;
@RequestMapping("/addNum")
public void addNum() {
System.out.println(++num);
}
} public class TestController {
private int num = 0;
private final ThreadLocal<Integer> uniqueNum = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return num;
}
};
@RequestMapping("/addNum")
public void addNum() {
int unum = uniqueNum.get();
uniqueNum.set(++unum);
System.out.println(uniqueNum.get());
}
}After applying these techniques, each request to /addNum yields a consistent result (e.g., always 1 when using ThreadLocal), preserving idempotency and preventing data races.
The overall recommendation is to avoid mutable state in Controllers or to manage it with thread‑safe constructs to ensure reliable behavior under high concurrency.
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.
Top Architect
Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn 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.
