Reproduction of a typical online Spring Bean object thread-safety issues (attach three solutions)

Recurring problem

Suppose a typical online Spring Boot Web items, the processing logic of a service is a:

To accept a string parameter name, and the value given to a subject injected bean, bean modify the properties and then return the object name, the period we used Thread.sleep(300)to simulate the time-consuming business line high

code show as below:

@RestController
@RequestMapping("name")
public class NameController {

    @Autowired
    private NameService nameService;

    @RequestMapping("")
    public String changeAndReadName (@RequestParam String name) throws InterruptedException {
        System.out.println("get new request: " + name);
        nameService.setName(name);
        Thread.sleep(300);
        return nameService.getName();
    }

}

Above nameService is also very simple, a common target of Spring Service

Specific code as follows:

@Service
public class NameService {

    private String name;

    public NameService() {
    }

    public NameService(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public NameService setName(String name) {
        this.name = name;
        return this;
    }
}

I believe used Spring Boot partners have any questions about this code will not actually run no problem, tests are also run through, but really on the line, but inside they will produce a thread-safety issues

Do not believe it, we passed the thread pool, open 200 threads to test NameController can be reproduced to

Test code as follows

    @Test
    public void changeAndReadName() throws Exception {
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(200, 300 , 2000, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(200));
        for (int i = 0; i < 200; i++) {
            poolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println(Thread.currentThread().getName() + " begin");
                        Map<String, String> headers = new HashMap<String, String>();
                        Map<String, String> querys = new HashMap<String, String>();

                        querys.put("name", Thread.currentThread().getName());
                        headers.put("Content-Type", "text/plain;charset=UTF-8");
                        HttpResponse response = HttpTool.doGet("http://localhost:8080",
                                "/name",
                                "GET",
                                headers,
                                querys);
                        String res = EntityUtils.toString(response.getEntity());

                        if (!Thread.currentThread().getName().equals(res)) {
                            System.out.println("WE FIND BUG !!!");
                            Assert.assertEquals(true, false);
                        } else {
                            System.out.println(Thread.currentThread().getName() + " get received " + res);
                        }
                    }catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            });
        }
        while(true) {
            Thread.sleep(100);
        }
    }

This test code, start 200 threads, for NameController test, each thread will thread its own name as a parameter to submit, and return the results to assert, if the value of the returned value is submitted does not match, then an exception is thrown AssertNotEquals

After the actual test, we found that more than half of the nearly 200 threads will throw an exception

Causes problems

First, we have to analyze, when a thread, to http: // localhost: 8080 / name when requesting, Spring Boot service line, will be 8.5 to receive this request through its built-in Tomcat

In Tomcat 8.5, the default is used NIO implementation, each corresponding to a server and request thread, then the server thread to the corresponding redistribution servlet processing request to

So we can assume that 200 concurrent client requests, enter NameController execution of the request, and it is divided into 200 different server threads to handle

But Bean objects that Spring offers, and there is no default implementation of its thread safety, that is, by default, our NameController with NameService belong to singleton object

This time should be well explained, and 200 threads simultaneously operating two singletons (a NameController objects, a NameService objects), without using any locking mechanism, does not produce thread-safety issues is not possible (unless it is regardless of the state of operation)

Problem Solution

In accordance with the instructions of the title, I am here to offer three solutions, namely,

  • synchronized modification methods

  • synchronized code block

  • Changes in the scope bean object

Next, each solution is described, including their respective advantages and disadvantages

synchronized modification methods

Use synchronized to a modification could produce thread-safety issues of method, it should be our most likely to think, but also the most simple solution, we only need in public String changeAndReadName (@RequestParam String name)this method, a synchronized modified to increase

The actual test, so really solve the problem, but if you can then think about a problem

When we come back to run the test code, find the program run efficiency is greatly reduced because all logic before each thread must wait for a thread to finish changeAndReadName () method before they can run, but this logic, we used to model contains the high time-consuming business Thread.sleep(300), but it has nothing to do with our thread-safe

In this case, we can use the second method to solve the problem

synchronized code block

The actual line of logic, often encounter such a situation: we need to ensure thread-safe code, the code with high time-consuming (for example, call a third party api), quite coincidentally written in the same method

In this case then, using synchronized code block, rather than directly modifying the method will come more efficient

Specific solutions codes are as follows:

    @RequestMapping("")
    public String changeAndReadName (@RequestParam String name) throws InterruptedException {
        System.out.println(Thread.currentThread().getName() + " get new request: " + name);
        String result = "";
        synchronized (this) {
            nameService.setName(name);
            result = nameService.getName();
        }
        Thread.sleep(300);
        return result;
    }

Run the test code again, we can find efficiencies basically solved the problem, but the disadvantage is the need for our own good grasp of what is probably a thread-safe code in question appears (The actual line logic can be very complex, this one is not good grasp)

Changes in the scope bean object

Now very unfortunate thing happened, we even took the code is high dependency status, but also the need to ensure efficiency, so in this case the problem can only be resolved by sacrificing a small amount of memory

Probably idea is by changing the bean object's scope, so that each server thread corresponds to a new bean object to handle logic, by one another unrelated to circumvent security thread

First of all we need to know what the scope bean object, please see table below

Scope Explanation
singleton The default scope, bean will in this case is defined as a singleton object, the object's life cycle is only (but for Spring lazy loading mechanism, only the first is consistent with the Spring IOC container creates)
prototype bean is defined will create a new object each time the injection is
request bean embodiment is defined to create a single object for each HTTP request, that is to say in a single request which are multiplexed a singleton object
session bean is defined to create objects within a single embodiment of a session lifecycle
application bean is defined as a single multiplexed embodiment ServletContext object lifecycle
              websocket               bean multiplexed is defined as a singleton object lifecycle websocket

After the bean scope objects clearly, then we only need to consider the question: What scope of the bean modification?

As I have explained, in this case, 200 server threads, default is operating two singleton bean objects are NameController and NameService (yes, in the Spring Boot, Controller default is singleton object)

It is not directly NameController and NameServie to prototype can it?

If your project is using Struts2, then do so without any problems, but can seriously affect performance in Spring MVC, Struts2 interception because the request is based on class, and Spring MVC is based on the method

So we should NameController scope is set to request, will NameService to prototype to solve

Specific operation code as follows

@RestController
@RequestMapping("name")
@Scope("request")
public class NameController {

}
@Service
@Scope("prototype")
public class NameService {

}

references

The original is not easy, reprint please state source

Case Item Code: GitHub / liumapp / Booklet

Guess you like

Origin blog.51cto.com/14483562/2426600