Today, I found that the use of spring beans has always been wrong. This is the right way!
Author: DayDayUp
Source: blog csdn. net/songzehao/article/details/103365494
Spring bean s are singleton by default. In some cases, singleton is unsafe for concurrency. Take Controller as an example. The root of the problem is that we may define member variables in Controller. In this way, when multiple requests come, they enter the Controller object of the same singleton and modify the value of this member variable, which will affect each other, The effect of concurrency safety (different from the concept of thread isolation, which will be explained later) cannot be achieved.
1, Throw a problem
First, take an example to prove the concurrency insecurity of a single instance:
@Controller public class HomeController { private int i; @GetMapping("testsingleton1") @ResponseBody public int test1() { return ++i; } }
When you visit this url several times, you can see that the result of each time is self increasing, so such code is obviously unsafe for concurrency.
2, Solution
Therefore, in order to keep the stateless massive Http requests unaffected, we can take the following measures:
2.1 single case variant prototype
For web projects, you can annotate @ Scope("prototype") or @ Scope("request") on the Controller class. For non web projects, you can annotate @ Scope("prototype") on the Component class.
Advantages: simple implementation;
Disadvantages: it greatly increases the server resource overhead of bean creation, instantiation and destruction.
2.2 thread isolation class ThreadLocal
Someone thought of the thread isolation class ThreadLocal. We tried to wrap the member variable as ThreadLocal to achieve concurrency safety. At the same time, we printed the thread name of the Http request. The modified code is as follows:
@Controller public class HomeController { private ThreadLocal<Integer> i = new ThreadLocal<>(); @GetMapping("testsingleton1") @ResponseBody public int test1() { if (i.get() == null) { i.set(0); } i.set(i.get().intValue() + 1); log.info("{} -> {}", Thread.currentThread().getName(), i.get()); return i.get().intValue(); } }
Visit this url several times to test one, and print the log as follows:
[INFO ] 2019-12-03 11:49:08,226 com.cjia.ds.controller.HomeController.test1(HomeController.java:50) http-nio-8080-exec-1 -> 1 [INFO ] 2019-12-03 11:49:16,457 com.cjia.ds.controller.HomeController.test1(HomeController.java:50) http-nio-8080-exec-2 -> 1 [INFO ] 2019-12-03 11:49:17,858 com.cjia.ds.controller.HomeController.test1(HomeController.java:50) http-nio-8080-exec-3 -> 1 [INFO ] 2019-12-03 11:49:18,461 com.cjia.ds.controller.HomeController.test1(HomeController.java:50) http-nio-8080-exec-4 -> 1 [INFO ] 2019-12-03 11:49:18,974 com.cjia.ds.controller.HomeController.test1(HomeController.java:50) http-nio-8080-exec-5 -> 1 [INFO ] 2019-12-03 11:49:19,696 com.cjia.ds.controller.HomeController.test1(HomeController.java:50) http-nio-8080-exec-6 -> 1 [INFO ] 2019-12-03 11:49:22,138 com.cjia.ds.controller.HomeController.test1(HomeController.java:50) http-nio-8080-exec-7 -> 1 [INFO ] 2019-12-03 11:49:22,869 com.cjia.ds.controller.HomeController.test1(HomeController.java:50) http-nio-8080-exec-9 -> 1 [INFO ] 2019-12-03 11:49:23,617 com.cjia.ds.controller.HomeController.test1(HomeController.java:50) http-nio-8080-exec-8 -> 1 [INFO ] 2019-12-03 11:49:24,569 com.cjia.ds.controller.HomeController.test1(HomeController.java:50) http-nio-8080-exec-10 -> 1 [INFO ] 2019-12-03 11:49:25,218 com.cjia.ds.controller.HomeController.test1(HomeController.java:50) http-nio-8080-exec-1 -> 2 [INFO ] 2019-12-03 11:49:25,740 com.cjia.ds.controller.HomeController.test1(HomeController.java:50) http-nio-8080-exec-2 -> 2 [INFO ] 2019-12-03 11:49:43,308 com.cjia.ds.controller.HomeController.test1(HomeController.java:50) http-nio-8080-exec-3 -> 2 [INFO ] 2019-12-03 11:49:44,420 com.cjia.ds.controller.HomeController.test1(HomeController.java:50) http-nio-8080-exec-4 -> 2 [INFO ] 2019-12-03 11:49:45,271 com.cjia.ds.controller.HomeController.test1(HomeController.java:50) http-nio-8080-exec-5 -> 2 [INFO ] 2019-12-03 11:49:45,808 com.cjia.ds.controller.HomeController.test1(HomeController.java:50) http-nio-8080-exec-6 -> 2 [INFO ] 2019-12-03 11:49:46,272 com.cjia.ds.controller.HomeController.test1(HomeController.java:50) http-nio-8080-exec-7 -> 2 [INFO ] 2019-12-03 11:49:46,489 com.cjia.ds.controller.HomeController.test1(HomeController.java:50) http-nio-8080-exec-9 -> 2 [INFO ] 2019-12-03 11:49:46,660 com.cjia.ds.controller.HomeController.test1(HomeController.java:50) http-nio-8080-exec-8 -> 2 [INFO ] 2019-12-03 11:49:46,820 com.cjia.ds.controller.HomeController.test1(HomeController.java:50) http-nio-8080-exec-10 -> 2 [INFO ] 2019-12-03 11:49:46,990 com.cjia.ds.controller.HomeController.test1(HomeController.java:50) http-nio-8080-exec-1 -> 3 [INFO ] 2019-12-03 11:49:47,163 com.cjia.ds.controller.HomeController.test1(HomeController.java:50) http-nio-8080-exec-2 -> 3 ......
From the log analysis, the results of more than 20 consecutive requests are 1, 2, 3, etc., and we expect that no matter how many concurrent requests I have, the result of each time is 1; At the same time, it can be found that the default request thread pool size of the web server is 10, and these 10 core threads can be reused by different Http requests, so this is why the results of the same thread name will not be repeated.
Summary: ThreadLocal can achieve thread isolation, but it still cannot achieve concurrency safety.
2.3 try to avoid using member variables
Some people say that the member variable of a singleton bean is so troublesome that it can be avoided as far as possible without using the member variable. If the business allows, it is easier to replace the member variable with the local variable in the RequestMapping method. This way is naturally the most appropriate, and I also recommend it most. The code is modified as follows:
@Controller public class HomeController { @GetMapping("testsingleton1") @ResponseBody public int test1() { int i = 0; // TODO biz code return ++i; } }
But what should we do when there are few cases where member variables must be used?
2.4 classes using concurrency security
As a highly functional programming language, Java has rich API s. If you do not want to use member variables in singleton bean s, you can consider using concurrent safe containers, such as ConcurrentHashMap, ConcurrentHashSet, etc, We can wrap our member variables (usually such variables as the currently running task list) into these concurrent and safe containers for management.
2.5 concurrent security of distributed or microservices
If we need to further consider the impact of microservices or distributed services, mode 4 is not enough to deal with it. Therefore, we can use distributed caching middleware that can share some information, such as Redis, so as to ensure that different service instances of the same service have the same shared information (such as currently running task list and other variables). In addition, welcome to the official account Java shrimp, the background reply to the "back-end interview", to send you an interview question treasure!
3, Supplementary notes
spring bean scope has the following five:
-
singleton: singleton mode. When spring creates an applicationContext container, spring will want to initialize all instances of the scope. Adding lazy init can avoid preprocessing;
-
Prototype: prototype mode. Each time the bean is obtained through getBean, a new instance will be generated. After creation, spring will no longer manage it;
(the following is only used under the web project)
-
Request: everyone engaged in the web should understand the domain of request, that is, a new instance is generated for each request. Unlike prototype, spring is still listening for the next management after creation;
-
Session: every session, the same as above;
-
global session: global web domain, similar to application in servlet.