Is the bean in Spring is the thread safe?

Conclusion: Not thread-safe

Is the Bean in the Spring container thread-safe? , The container itself does not provide a bean thread safety strategy, so it can be said that the Bean in the Spring container does not have thread safety features, but the specific scope of the Bean should be studied.

Spring’s bean scope (scope) type
  1, singleton: singleton, default scope.

  2, prototype: prototype, create a new object each time.

  3, request: request, each Http request creates a new object, suitable for WebApplicationContext environment.

  4, session: session, the same session shares one instance, and different sessions use different instances.

  5, global-session: global session, all sessions share one instance.

The issue of thread safety should be explained separately from singleton and prototype Bean.

Prototype Bean
   For prototype Beans, each time a new object is created, that is, there is no Bean sharing between threads, and naturally there will be no thread safety issues.

Singleton Bean
   For a singleton bean, all threads share a singleton instance bean, so there is competition for resources.

   If a singleton bean is a stateless bean, that is, operations in the thread will not be performed on the members of the bean For operations other than query, then this singleton bean is thread-safe. For example, Spring mvc’s Controller, Service, Dao, etc., these Beans are mostly stateless and only focus on the method itself.

Spring singleton, why controller, service and dao can guarantee thread Safety?

   Beans in Spring are in singleton mode by default, and the framework does not encapsulate beans in multiple threads.
   In fact, most of the time Beans are stateless (such as Dao). So to some extent, Beans are actually safe.
   But if the Bean is stateful, the developer needs to ensure thread safety by himself. The easiest way is to change the scope of the bean and change “singleton” to “protopyte” so that every time the bean is requested is equivalent So new Bean() can ensure thread safety in this way.

   Stateful means to have data storage function
   Stateless means not to save data
  

   The controller, service and dao layers themselves are not thread-safe, but if only Calling the method inside, and multi-threading to call an instance method will copy variables in the memory. This is the working memory of your own thread and is safe.

   If you want to understand the principle, you can see “In-depth understanding of the JVM virtual machine”, section 2.2.2:

Java virtual machine stack is thread private, Its life cycle is the same as that of a thread. The virtual machine stack describes the memory model of Java method execution: when each method is executed, a stack frame is created to store information such as the local variable table, operand stack, dynamic link, and method exit. 

   “Java Concurrent Programming Practice” Section 3.2.2:

One ​​of the inherent properties of local variables is to be enclosed in the thread of execution.

They are located in the stack of the execution thread, and other threads cannot access this stack.

   So in fact, any stateless singleton is thread-safe.
  The root of Spring is to build a system through a large number of such singletons, and provide services in the form of transaction scripts

You can also read this article to deepen your understanding: About Spring @Controller @Service and other thread safety issues

First ask @Controller @Service, is it thread safe?
   Answer: It is not in the default configuration. why? Because @Controller does not add @Scope by default, without @Scope is the default value singleton, singleton. This means that the system will only initialize the Controller container once, so every time it requests the same Controller container, it is of course not thread-safe. For example:

@RestController

public class TestController {

private int var = 0;

@GetMapping(value
= "/test_var")
public String test() {
System.out.println(
"common variable var:" + (++var));
return "Common variable var:" + var;
}
}

Send three requests in postman and the results are as follows:

Ordinary variable var:1
Ordinary variable var: 2
Ordinary variable var: 3

It shows that it is not thread-safe. How to do it? You can add the @Scope annotation mentioned above, as follows:   

@RestController

@Scope(value
= "prototype") // plus @ Scope annotation, he has 2 values: singleton-singleton multi-instance-prototype
public class TestController {

private int var = 0;

@GetMapping(value
= "/test_var")
public String test() {
System.out.println(
"common variable var:" + (++var));
return "Common variable var:" + var;
}
}

In this way, each request creates a separate Controller container, so each request is thread-safe, and the results of the three requests: p>

common variable var:1
Ordinary variable var: 1
Ordinary variable var: 1

Is the prototype with many @Scope annotations necessarily thread-safe?  

@RestController

@Scope(value
= "prototype") // plus @ Scope annotation, he has 2 values: singleton-singleton multi-instance-prototype
public class TestController {
private int var = 0;
private static int staticVar = 0;

@GetMapping(value
= "/test_var")
public String test() {
System.out.println(
"common variable var:" + (++var)+ "---static variable staticVar:" + (++staticVar));
return "Normal variable var:" + var + "static variable staticVar:" + staticVar;
}
}

See the results of three requests:

Ordinary variable var:1---static variable staticVar:1
Ordinary variable var:1---static variable staticVar:2
Ordinary variable var:1---static variable staticVar:3

   Although a Controller is created separately every time, it cannot hold that the variable itself is static. So, even if you add @Scope annotations, it may not guarantee Controller 100. % Thread safety. So whether thread safety depends on how to define variables and Controller configuration. So let’s have a little experiment, the code is as follows:   

@RestController

@Scope(value
= "singleton") // prototype singleton< /span>
public class TestController {

private int var = 0; // Define an ordinary variable

private static int staticVar = 0; // Define a static variable

@Value(
"${test-int}")
private int testInt; // Read variables from the configuration file

ThreadLocal
tl = new ThreadLocal<>(); / / Use ThreadLocal to encapsulate variables

@Autowired
private User user; // Inject an object to encapsulate variables

@GetMapping(value
= "/test_var")
public String test() {
tl.set(
1);
System.out.println(
"First take the value in the user object: "+user.getAge()+"=== take another hashCode:"+user.hashCode());
user.setAge(
1);
System.out.println(
"common variable var:" + (++var) + "===static variable staticVar:" + (++staticVar) + "===configuration variable testInt :" + (++testInt)
+ "===ThreadLocal variable tl:" + tl.get()+"===Inject variable user:" + user.getAge ());
return "Normal variable var:" + var + ", static variable staticVar:" + staticVar + ", configuration read variable testInt:" + testInt + ",ThreadLocal variable tl:"
+ tl.get() + "Inject variable user:" + user.getAge();
}
}

Add code other than Controller:
Bean:User defined in config

< pre>@Configuration

public class MyConfig {

@Bean

public User user(){

return new User();

}

}

There are so many ways to define variables that I can think of temporarily. The results of three http requests are as follows:

 first take the value in the user object: 0=== take another hashCode:241165852
Ordinary variable var:1===Static variable staticVar:1===Configuration variable testInt:1===ThreadLocal variable tl:1===Inject variable user:1
First take the value in the user object: 1=== and then take the hashCode:241165852
Ordinary variable var:2===static variable staticVar:2===configuration variable testInt:2===ThreadLocal variable tl:1===Inject variable user:1
First take the value in the user object: 1=== and then take the hashCode:241165852
Ordinary variable var:3===Static variable staticVar:3===Configuration variable testInt:3===ThreadLocal variable tl:1===Inject variable user:1

   It can be seen that in the singleton mode, only the variables encapsulated by ThreadLocal in the Controller are thread-safe. Why do you say this way? We can see that in the results of the 3 requests, only the value of the ThreadLocal variable is from 0+1=1 each time, and the others are cumulative. For the user object, the default value is 0, and the second value is selected. At that time, it is already 1. The key is that his hashCode is the same, which means that each request calls the same user object.
The following will change the @Scope annotation attribute on TestController to multi-instance: @Scope(value = “prototype”), everything else remains the same, request again, the result is as follows:

 first take the value in the user object: 0=== take another hashCode:853315860
Ordinary variable var:1===Static variable staticVar:1===Configuration variable testInt:1===ThreadLocal variable tl:1===Inject variable user:1
First take the value in the user object: 1=== and then take the hashCode: 853315860
Ordinary variable var:1===Static variable staticVar:2===Configuration variable testInt:1===ThreadLocal variable tl:1===Inject variable user:1
First take the value in the user object: 1=== and then take the hashCode: 853315860
Ordinary variable var:1===Static variable staticVar:3===Configuration variable testInt:1===ThreadLocal variable tl:1===Inject variable user:1

  Analyzing this result, it is found that ordinary variables in multi-instance mode, configuration variables and ThreadLocal variables are all thread-safe, while static variables and user (see his hashCode are the same The variables in the object are not thread-safe. That is to say, although TestController initializes an object every time it is requested, there is always only one copy of static variables, and there is only one copy of the injected user object. Of course, there is only one static variable, so is there a way to make the user object a new one every time? Of course you can:

public class MyConfig {

@Bean
@Scope(value
= "prototype")
public User user(){
return new User();
}
}

Add the same annotation to the injected Bean in config@Scope(value = "prototype") That’s it, let’s ask for it again:

take the value in the user object first: 0===again Take a hashCode: 1612967699
Ordinary variable var:1===Static variable staticVar:1===Configuration variable testInt:1===ThreadLocal variable tl:1===Inject variable user:1
First take the value in the user object: 0=== and then take the hashCode: 985418837
Ordinary variable var:1===Static variable staticVar:2===Configuration variable testInt:1===ThreadLocal variable tl:1===Inject variable user:1
First take the value in the user object: 0=== and then take the hashCode: 1958952789
Ordinary variable var:1===Static variable staticVar:3===Configuration variable testInt:1===ThreadLocal variable tl:1===Inject variable user:1

You can see that the hashCode of the user object requested each time is not the same, and the variable value in the user before each assignment is also the default value of 0.
Summarize below:

   1. In containers such as @Controller/@Service, the scope value is singleton-singleton by default, which is also thread-unsafe.  2. Try not to define static variables in containers such as @Controller/@Service. It is not thread-safe whether it is a singleton or a prototype.  3. Bean objects injected by default are also thread-unsafe when the scope is not set.  4. If you must define a variable, use ThreadLocal to encapsulate it. This is thread-safe

The Java virtual machine stack is private to the thread, and its life cycle is the same as that of the thread. The virtual machine stack describes the memory model of Java method execution: when each method is executed, a stack frame is created to store information such as the local variable table, operand stack, dynamic link, and method exit. 

One ​​of the inherent properties of local variables is that they are enclosed in the thread of execution.

They are located in the stack of the execution thread, and other threads cannot access this stack.

@RestController

public class TestController {

private int var = 0;

@GetMapping(value
= "/test_var")
public String test() {
System.out.println(
"common variable var:" + (++var));
return "Common variable var:" + var;
}
}

common variable var:1
Ordinary variable var: 2
Ordinary variable var: 3

@RestController

@Scope(value
= "prototype") // plus @ Scope annotation, he has 2 values: singleton-singleton multi-instance-prototype
public class TestController {

private int var = 0;

@GetMapping(value
= "/test_var")
public String test() {
System.out.println(
"common variable var:" + (++var));
return "Common variable var:" + var;
}
}

common variable var:1
Ordinary variable var: 1
Ordinary variable var: 1

@RestController

@Scope(value
= "prototype") // plus @ Scope annotation, he has 2 values: singleton-singleton multi-instance-prototype
public class TestController {
private int var = 0;
private static int staticVar = 0;

@GetMapping(value
= "/test_var")
public String test() {
System.out.println(
"common variable var:" + (++var)+ "---static variable staticVar:" + (++staticVar));
return "Normal variable var:" + var + "static variable staticVar:" + staticVar;
}
}

Ordinary variable var:1---static variable staticVar:1
Ordinary variable var:1---static variable staticVar:2
Ordinary variable var:1---static variable staticVar:3

@RestController

@Scope(value
= "singleton") // prototype singleton< /span>
public class TestController {

private int var = 0; // Define an ordinary variable

private static int staticVar = 0; // Define a static variable

@Value(
"${test-int}")
private int testInt; // Read variables from the configuration file

ThreadLocal
tl = new ThreadLocal<>(); / / Use ThreadLocal to encapsulate variables

@Autowired
private User user; // Inject an object to encapsulate variables

@GetMapping(value
= "/test_var")
public String test() {
tl.set(
1);
System.out.println(
"First take the value in the user object: "+user.getAge()+"=== take another hashCode:"+user.hashCode());
user.setAge(
1);
System.out.println(
"common variable var:" + (++var) + "===static variable staticVar:" + (++staticVar) + "===configuration variable testInt :" + (++testInt)
+ "===ThreadLocal variable tl:" + tl.get()+"===Inject variable user:" + user.getAge ());
return "Normal variable var:" + var + ", static variable staticVar:" + staticVar + ", configuration read variable testInt:" + testInt + ",ThreadLocal variable tl:"
+ tl.get() + "Inject variable user:" + user.getAge();
}
}

@Configuration

public class MyConfig {
@Bean
public User user(){
return new User();
}
}

take the value in the user object first: 0===take it again hashCode:241165852
Ordinary variable var:1===Static variable staticVar:1===Configuration variable testInt:1===ThreadLocal variable tl:1===Inject variable user:1
First take the value in the user object: 1=== and then take the hashCode:241165852
Ordinary variable var:2===static variable staticVar:2===configuration variable testInt:2===ThreadLocal variable tl:1===Inject variable user:1
First take the value in the user object: 1=== and then take the hashCode:241165852
Ordinary variable var:3===Static variable staticVar:3===Configuration variable testInt:3===ThreadLocal variable tl:1===Inject variable user:1

First take the value in the user object: 0=== take another hashCode:853315860
Ordinary variable var:1===Static variable staticVar:1===Configuration variable testInt:1===ThreadLocal variable tl:1===Inject variable user:1
First take the value in the user object: 1=== and then take the hashCode: 853315860
Ordinary variable var:1===Static variable staticVar:2===Configuration variable testInt:1===ThreadLocal variable tl:1===Inject variable user:1
First take the value in the user object: 1=== and then take the hashCode: 853315860
Ordinary variable var:1===Static variable staticVar:3===Configuration variable testInt:1===ThreadLocal variable tl:1===Inject variable user:1

public class MyConfig {

@Bean
@Scope(value
= "prototype")
public User user(){
return new User();
}
}

take the value in the user object first: 0===take it again hashCode:1612967699
Ordinary variable var:1===Static variable staticVar:1===Configuration variable testInt:1===ThreadLocal variable tl:1===Inject variable user:1
First take the value in the user object: 0=== and then take the hashCode: 985418837
Ordinary variable var:1===Static variable staticVar:2===Configuration variable testInt:1===ThreadLocal variable tl:1===Inject variable user:1
First take the value in the user object: 0=== and then take the hashCode: 1958952789
Ordinary variable var:1===Static variable staticVar:3===Configuration variable testInt:1===ThreadLocal variable tl:1===Inject variable user:1

Leave a Comment

Your email address will not be published.