background
I have been using Spring MVC framework for nearly half a year, but I have never encountered thread safety related problems.
Until this week, we made such a demand:
- Our platform sells recharge codes. We need to regularly check the remaining inventory of recharge codes of each product. If the inventory is too low, we will alarm / get off the shelf;
- To query the remaining inventory, you need to traverse the records and compare the cost performance. You can't query every award;
- The conventional scheme is to poll every fixed time, such as every minute, but I think it's better to query every fixed recharge code, such as every 10 recharge codes issued by a single machine.
- If so, each machine needs to maintain a thread safe dictionary to record how many times each recharge code is issued.
(it would be nice if we maintained a "remaining inventory" number, but unfortunately we didn't)
Here I think, in fact, you don't need to traverse every time you query the remaining inventory! As long as the number found is enough, you can use to traverse "how many in the end". You can change another version according to this idea, but that's what we'll talk about later.
experiment
Experiment 1: private variables in @ Component are not thread safe by default
The experimental code is as follows. A private variable counter is defined in the controller to print the thread ID and counter value respectively when processing the request:
@RequestMapping("/test") @RestController @Slf4j public class TestController { private Integer counter = 0; // Private variable counter @GetMapping("/test") public String test() { counter ++; // counter ++ // Print thread ID and counter values respectively log.info("thread: {}, counter value: {}", Thread.currentThread().getId(), counter); return "asdfpoiu"; } }
Continuously request the above / test/test interface for 13 times:
2021-07-23 18:23:24.641 - INFO 17992 --- [nio-8157-exec-2] **.controllers.TestController thread: 55, counter value: 1 2021-07-23 18:23:27.359 - INFO 17992 --- [nio-8157-exec-3] **.controllers.TestController thread: 56, counter value: 2 2021-07-23 18:23:30.085 - INFO 17992 --- [nio-8157-exec-4] **.controllers.TestController thread: 57, counter value: 3 2021-07-23 18:23:31.787 - INFO 17992 --- [nio-8157-exec-5] **.controllers.TestController thread: 58, counter value: 4 2021-07-23 18:23:33.563 - INFO 17992 --- [nio-8157-exec-6] **.controllers.TestController thread: 59, counter value: 5 2021-07-23 18:23:35.721 - INFO 17992 --- [nio-8157-exec-7] **.controllers.TestController thread: 60, counter value: 6 2021-07-23 18:23:38.342 - INFO 17992 --- [nio-8157-exec-8] **.controllers.TestController thread: 61, counter value: 7 2021-07-23 18:23:40.799 - INFO 17992 --- [nio-8157-exec-9] **.controllers.TestController thread: 62, counter value: 8 // The following line is very interesting. exec-10 occupies more than one place, resulting in nio becoming io 2021-07-23 18:23:45.816 - INFO 17992 --- [io-8157-exec-10] **.controllers.TestController thread: 63, counter value: 9 2021-07-23 18:23:47.434 - INFO 17992 --- [nio-8157-exec-1] **.controllers.TestController thread: 54, counter value: 10 2021-07-23 18:23:51.058 - INFO 17992 --- [nio-8157-exec-2] **.controllers.TestController thread: 55, counter value: 11 2021-07-23 18:24:37.291 - INFO 17992 --- [nio-8157-exec-5] **.controllers.TestController thread: 58, counter value: 12 2021-07-23 18:24:39.401 - INFO 17992 --- [nio-8157-exec-6] **.controllers.TestController thread: 59, counter value: 13
It's interesting to find that Spring uses 10 different threads (IDS 54 ~ 63) when processing the first 10 requests. It doesn't reuse the previous threads until the 11th request.
Moreover, you can see that the value of counter increases from 0 to 13, indicating that the private variable is shared by all threads. Problems can occur when a large number of requests are concurrent.
Link: Spring Boot view thread pool size
In my project, the maximum number of threads is 1000. I don't know why there are only 10 threads above. It may be set elsewhere:
server: tomcat: basedir: max-threads: 1000
Experiment 2: ThreadLocal
Thread insecurity can be solved by using ThreadLocal, but this means that counter is no longer shared between threads:
@RequestMapping("/test") @RestController @Slf4j public class TestController { private ThreadLocal<Integer> counter = ThreadLocal.withInitial(() -> 0); // Thread independent variable @GetMapping("/test") public String test() { counter.set(counter.get() + 1); log.info("thread: {}, counter value: {}", Thread.currentThread().getId(), counter.get()); return "asdfpoiu"; } }
Similarly, after 13 requests, it is found that the value of the counter becomes unique to the thread:
2021-07-23 19:27:07.674 - INFO 15424 --- [nio-8157-exec-2] **.controllers.TestController thread: 52, counter value: 1 2021-07-23 19:27:11.308 - INFO 15424 --- [nio-8157-exec-3] **.controllers.TestController thread: 53, counter value: 1 2021-07-23 19:27:12.618 - INFO 15424 --- [nio-8157-exec-4] **.controllers.TestController thread: 54, counter value: 1 2021-07-23 19:27:13.861 - INFO 15424 --- [nio-8157-exec-5] **.controllers.TestController thread: 55, counter value: 1 2021-07-23 19:27:14.970 - INFO 15424 --- [nio-8157-exec-6] **.controllers.TestController thread: 56, counter value: 1 2021-07-23 19:27:17.454 - INFO 15424 --- [nio-8157-exec-7] **.controllers.TestController thread: 57, counter value: 1 2021-07-23 19:27:19.245 - INFO 15424 --- [nio-8157-exec-8] **.controllers.TestController thread: 58, counter value: 1 2021-07-23 19:27:20.130 - INFO 15424 --- [nio-8157-exec-9] **.controllers.TestController thread: 59, counter value: 1 2021-07-23 19:27:20.876 - INFO 15424 --- [io-8157-exec-10] **.controllers.TestController thread: 60, counter value: 1 2021-07-23 19:27:21.596 - INFO 15424 --- [nio-8157-exec-1] **.controllers.TestController thread: 51, counter value: 1 2021-07-23 19:27:22.337 - INFO 15424 --- [nio-8157-exec-2] **.controllers.TestController thread: 52, counter value: 2 2021-07-23 19:27:23.960 - INFO 15424 --- [nio-8157-exec-3] **.controllers.TestController thread: 53, counter value: 2 2021-07-23 19:27:24.573 - INFO 15424 --- [nio-8157-exec-4] **.controllers.TestController thread: 54, counter value: 2
Experiment 3: AtomicInteger
AtomicInteger can also solve the problem of thread insecurity and ensure that each thread updates the counter value in turn:
@RequestMapping("/test") @RestController @Slf4j public class TestController { private AtomicInteger counter = new AtomicInteger(0); // Atomic Integer @GetMapping("/test") public String test() { log.info("thread: {}, counter value: {}", Thread.currentThread().getId(), counter.incrementAndGet()); return "asdfpoiu"; } }
A large number of concurrent requests are not simulated here, but 13 times in sequence. In this way, the effect is actually the same as that in Experiment 1:
2021-07-23 19:41:25.207 - INFO 23992 --- [nio-8157-exec-1] **.controllers.TestController thread: 52, counter value: 1 2021-07-23 19:41:26.405 - INFO 23992 --- [nio-8157-exec-2] **.controllers.TestController thread: 53, counter value: 2 2021-07-23 19:41:27.507 - INFO 23992 --- [nio-8157-exec-4] **.controllers.TestController thread: 55, counter value: 3 2021-07-23 19:41:28.315 - INFO 23992 --- [nio-8157-exec-6] **.controllers.TestController thread: 57, counter value: 4 2021-07-23 19:41:29.195 - INFO 23992 --- [nio-8157-exec-5] **.controllers.TestController thread: 56, counter value: 5 2021-07-23 19:41:29.943 - INFO 23992 --- [nio-8157-exec-7] **.controllers.TestController thread: 58, counter value: 6 2021-07-23 19:41:30.710 - INFO 23992 --- [nio-8157-exec-8] **.controllers.TestController thread: 59, counter value: 7 2021-07-23 19:41:31.342 - INFO 23992 --- [nio-8157-exec-9] **.controllers.TestController thread: 60, counter value: 8 2021-07-23 19:41:32.235 - INFO 23992 --- [io-8157-exec-10] **.controllers.TestController thread: 61, counter value: 9 2021-07-23 19:41:33.034 - INFO 23992 --- [nio-8157-exec-3] **.controllers.TestController thread: 54, counter value: 10 2021-07-23 19:41:33.871 - INFO 23992 --- [nio-8157-exec-1] **.controllers.TestController thread: 52, counter value: 11 2021-07-23 19:41:34.621 - INFO 23992 --- [nio-8157-exec-2] **.controllers.TestController thread: 53, counter value: 12 2021-07-23 19:41:35.512 - INFO 23992 --- [nio-8157-exec-4] **.controllers.TestController thread: 55, counter value: 13