Why Spring MVC Controllers Are Singleton and How to Ensure Thread Safety
This article explains that Spring MVC controllers are singleton by default, illustrates the thread‑safety problems caused by shared instance variables with concrete code examples, and provides practical solutions such as avoiding state, using prototype scope, ThreadLocal, or AtomicInteger to make controllers safe under high concurrency.
Singleton pattern is a key design pattern often examined in Java interviews; a common question is whether Spring MVC controllers are singleton or prototype.
Each incoming request requires a thread for its duration. If more simultaneous requests arrive than available threads, Tomcat creates additional threads up to maxThreads ; excess requests are queued up to acceptCount , after which connections are refused.
In a Tomcat container each servlet is a singleton, and Spring MVC controllers are singleton by default. Using the singleton pattern saves memory and improves resilience under high concurrency.
Controller is not thread‑safe
Because the controller is a singleton, instance variables are shared across threads. Defining mutable fields can lead to race conditions and break idempotency.
@Controller
public class TestController {
private int num = 0;
@RequestMapping("/addNum")
public void addNum() {
System.out.println(++num);
}
}When running locally, the first request to /addNum returns 1, the second returns 2, showing that the shared num variable is modified by each request, breaking the expected behavior.
All requests access the same controller instance, so the private member variable is shared among threads.
A thread that modifies the variable affects the value seen by other concurrent requests.
Solutions for controller concurrency safety
To guarantee thread safety, consider the following approaches:
Avoid defining instance variables in the controller.
If a non‑static variable is necessary, annotate the controller with @Scope("prototype") to make it prototype scoped.
Use a ThreadLocal variable so each thread has its own copy.
Define the variable as AtomicInteger and use its atomic operations.
@Controller
@Scope("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 consistently returns the same result, demonstrating proper thread‑safe behavior. In summary, avoid mutable instance variables in controllers whenever possible.
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.
