SpringMVC: How to ensure the concurrency security of Controller

Singleton mode (Singleton) is a very important design pattern in program design, and design pattern is also an aspect of Java interview focus. A question that is often asked in interviews is: Is the Controller in SpringMVC a single instance or multiple instances? Many students may take it for granted that the Controller is multiple instances, but it is not.

picture

Tomcat official website screenshot

According to the introduction on Tomcat's official website, for a browser request, tomcat will designate a processing thread, or select an idle thread in the thread pool, or create a new thread.

In the Tomcat container, each servlet is a singleton. In SpringMVC, Controller is also a singleton by default . The biggest advantage of using the singleton mode is that it can greatly save memory resources in high-concurrency scenarios and improve the service's ability to withstand pressure.

The problem that the singleton mode is prone to is: the instance variables defined in the Controller will compete for access when multiple requests are concurrent, and the instance variables in the Controller are not thread-safe.

Controller is not thread safe

Just because Controller is a singleton by default, it is not thread-safe. If you use the SpringMVC Controller, try not to use instance variables in the Controller, otherwise thread insecurity will occur, resulting in data logic confusion.

To give a simple example, define a non-static member variable num in a Controller. Increase num through the Controller member method.

@Controller
public class TestController {
    
    
    private int num = 0;

    @RequestMapping("/addNum")
    public void addNum() {
    
    
        System.out.println(++num);
    }
}

After running locally:

  • First visit http://localhost:8080/addNum, the answer is 1;
  • Visit http://localhost:8080/addNum again, and the answer is 2.
  • The results of the two accesses are different, num has been modified, which is not the result we expected, and the idempotency of the interface is destroyed.

As can be seen from this example, all requests access the same Controller instance, and the private member variables of the Controller are shared by threads. If the thread corresponding to a certain request modifies this variable, the modified value of this variable can also be read in other requests.

Controller Concurrency Security Solution

If you want to ensure the thread safety of Controller, there are the following solutions:

  • Try not to define member variables in Controller;
  • If you must define a non-static member variable, you can set the Controller to a multi-instance mode by annotating @Scope("prototype").

@Controller
@Scope(value="prototype")
public class TestController {
    
    
    private int num = 0;

    @RequestMapping("/addNum")
    public void addNum() {
    
    
        System.out.println(++num);
    }
}

The Scope attribute is used to declare the limited scenarios where the objects (Bean) in the IOC container are allowed to exist, or the living space of the objects. The IOC container generates and assembles objects before they enter the appropriate usage scenarios; the container typically destroys the objects when the object is no longer bound by those usage scenarios.

Controller is also a Bean, and the default Scope property is Singleton , which is the singleton mode. If the Scope attribute of the Bean is set to prototype, the container will regenerate a new object to the requester each time it receives a request for this type of object.

The ThreadLocal variable is used in the Controller . Each thread has a copy of the variable.


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 the above code is run, every time http://localhost:8080/addNum is requested, the result is 1.

A more strict approach is to define member variables with the AtomicInteger type, and use the auto-increment method of AtomicInteger to complete the operation on member variables.

In general, it is better not to define member variables in Controller as much as possible.

Guess you like

Origin blog.csdn.net/qq_43842093/article/details/132136406