content
- stateless object
- immutable object
- thread-specific object
- Possible problems with thread-specific objects
stateless object
The difference between stateful and stateless: stateful - will store data, stateless - will not store data
An object is the encapsulation of operations and data (object = operation + data). The data contained in the object is called the state of the object. It contains the data in the instance variables and static variables of the object, and may also contain references to other variables in the object. instance variables or static variables. If an instance of a class is shared by multiple threads and there is no shared state, then it is called a stateless object.
这个类的对象是一个有状态对象
class A{
Integer age;
}
这个类的对象中会引用其它有状态的对象所以它是一个由状态对象
@Component
class B{
@Autowire
private A a;
}
只有操作没有状态 - 无状态
class C{
public void test(){
......
}
}
本身和引用都是无状态 - 无状态
@Component
class D{
@Autowire
private C c;
}
A class may still have shared state even if there are no instance variables or static variables, as follows
enum Singleton{
INSTANCE;
private EnumSingleton singletonInstance;
Singleton(){
singletonInstance = new EnumSingleton();
}
public EnumSingleton getInstance(){
//对singletonInstance做一些配置操作
//例如 singletonInstance.num++;
return singletonInstance;
}
}
class SingletonTest{
public void test(){
Singleton st = Singleton.INSTANCE;
st.getInstance();
}
}
The INSTANCE of an enum will only be instantiated once, so if there is no operation corresponding to the annotation, this is a perfect singleton, and references to it by other classes are stateless. However, if the singletonInstance variable is manipulated in the getInstance method, there will be thread safety problems in the singletonInstance in a multi-threaded environment. The problem is more obvious in the following example, where the num variable is a variable that shares state.
enum Singleton{
INSTANCE;
private int num;
public int doSomething(){
num++;
return num;
}
}
class SingletonTest{
public void test(){
Singleton st = Singleton.INSTANCE;
st.doSomething();
}
}
Another situation is the use of static variables. Static variables are directly associated with classes and will not change with the creation of instances. Therefore, when Test does not have instance variables and static variables, it can be used directly through the class name in the method. Manipulating static variables will still cause thread safety problems, that is, there are calls to shared state variables in Test.
class A{
static int num;
}
class Test{
public void doSomething(){
A.num++;
}
}
Summary: A stateless object must not contain any instance variables or updatable static variables (including instance variables or static variables from the upper class of the corresponding class). However, a class that does not contain any instance variables or static variables is not necessarily a stateless object.
Using stateless classes and classes with only static methods
The servlet class is a typical application of stateless objects. Servlets are generally hosted by a web server (tomcat) to control their life cycles such as creation, operation, and destruction. Generally, only one instance of a servlet class is hosted in a web server, but it It will be accessed by multiple threads, and its method of processing requests, service, is not decorated with locks. If this class contains instance variables or static variables, thread safety problems will arise, so in general, Servlet instances are stateless objects.
immutable object
Immutable objects are objects whose state cannot be changed once created.
Immutable objects satisfy the following conditions
- Classes are modified by final fields to prevent changing the class definition through inheritance
- All member variables need to be modified by final. On the one hand, it ensures that the variable cannot be modified. On the other hand, when the final property is visible to other threads, it must be initialized (some blog posts must be private+final, but final ensures the data). Unchangeable).
- If this field is a mutable reference object, you need to decorate the field with private and do not provide a method to modify the state of the reference object.
The object does not escape during the initialization process (to prevent the object from being modified during the initialization process (anonymous class)): an object that has not been initialized is perceived by other threads, which is called object escape, which may lead to Program run error. Here's how an object might escape.
- Assign this to a shared variable in the constructor
- Pass this as a parameter to other methods in the constructor
- Start an anonymous class-based thread in the constructor
The following is an immutable object. Normally, after we create an instance, we cannot change its state, so if we need to modify it, we can only recreate an instance to replace it.
final class Score{
final int score;
private final Student student;
public Score(int score,Student student){
this.score = score;
this.student = student;
}
public String getName(){
return student.getName();
}
}
class test(){
......
public void update(){
Score s = new Score(...);
}
}
The use of immutable objects can have both positive and negative effects on the efficiency of garbage collection.
Negative: Since you have to recreate a new immutable object whenever you want to make an update to an immutable object, creating objects too frequently will increase the frequency of garbage collection.
Positive: Generally speaking, if there is a member variable in an object that is an object reference, then generally in a mutable object, the reference object is in the young generation, and the mutable object itself is in the old generation, but the immutable object is generally the reference object in the old generation , the immutable object itself is in the young generation. When modifying the value of an instance variable of a variable state object, if the object is already in the old generation, then when the garbage collector performs the next round of minor collection (Minor Collection), the old generation contains the object's value. All objects in the card (Card, the storage unit for storing objects in the old generation, the size of a Card is 512 bytes) must be scanned to determine whether there is an object in the old generation that holds a reference to the object to be recycled. Therefore, old generation objects holding references to young generation objects incur increased overhead for minor collections.
The iterator pattern can be used to reduce the memory footprint of immutable objects1
thread-specific object
For a non-thread-safe object, each object that accesses it creates an instance of the object, and each thread can only access the object instance created by itself. This object instance is called a thread-specific object (TSO, Thread Specific). Object) or thread-local variables.
ThreadLoacl\<T> is equivalent to the proxy for the thread to access its specific object, and the thread can create and access the specific object of its own thread through this object.
A ThreadLocal instance provides each thread that accesses it with a thread-specific object for that thread.
method | Features |
---|---|
public T get() | Gets the thread-specific object of the current thread associated with this thread-local variable |
public void set(T value) | Re-associate the thread-specific object of the current thread corresponding to the thread-local variable |
protected T initialValue() | The return value (object) of this method is the thread-specific object of the current thread corresponding to the thread-local variable in the initial state |
public void remove() | Removes the association between this thread-local variable and the corresponding thread-specific object of the current thread |
Simple usage and source code analysis of ThreadLocal
public class ThreadLocalDemo {
public static void main(String[] args) {
Thread t = new Thread(){
@Override
public void run() {
A a = new A();
a.testA();
A.TL.remove();
}
};
t.setName("t1");
t.start();
}
}
class A{
final static ThreadLocal<String> TL = new ThreadLocal<String>(){
@Override
protected String initialValue() {
return "A";
}
};
void testA(){
String str = TL.get();
System.out.println("str = " + str);
TL.set("B");
str = TL.get();
System.out.println("str = " + str);
}
}
--------------------------------------
str = A
str = B
As above, we create a new ThreadLocal instance in the ThreadLocalDemo class and use the anonymous class to override the initialValue method. Let's debug to see the flow of the entire sample.
Put a breakpoint on the get method at the location above
Because the threadLocals corresponding to this thread is a null value, first enter the setInitialValue method to initialize it.
The main thing in this method is to get the object we want to proxy. If the initialValue() method is not rewritten when declaring ThreadLocal, a null value is obtained in this place. Then create a ThreadlocalMap instance for the current thread and write the element, and finally return the object instance, and the ThreadLocal.get() method gets the value.
Then we enter the set method to observe the process
It will also first obtain the threadlocals of the current thread to determine whether it is empty. If it is empty, it will help him initialize one, and store the parameters of the set in threadlocals, otherwise, it will directly store Threadlocal→value in this container.
The process of the set method is basically to insert or modify the object instance ("B") to be represented by our ThreadLocal(TL) instance into the Entry array of ThreadLocalMap, and then determine the object instance in the array through the hashcode of ThreadLocal(TL) s position.
Based on the traceback of treadLocalHashCode, we found that it is a self-incrementing static variable in the ThreadLocal class.
The above is the search for the relevant threadLocal instance in the thread's storage entry array, and the insertion and replacement method:
- When a corresponding entry in threadLocal is found, the value will be replaced directly
- When an invalid entry is found during traversal, replace the key and value of the entry
- Without the above two cases, insert a new entry new Entry(key, value) in the null slot
After inserting a new entry, it is necessary to traverse the Entry array to find the Entry whose value is null, and set the corresponding entry to null, and then determine whether the Entry array needs to be expanded.
When the set is over, call get again to get the instance object of the ThreadLocal proxy, because the threadlocals of the previous thread have been initialized and there is an Entry corresponding to the current ThreadLocal (TL) object (the first get is called when initValue is initialized to A and then set is B), so the instance object ("B") of the target proxy can be obtained directly.
Generally, thread-local variables are declared as static variables, because they are only created once when the class is loaded. If they are declared as instance variables, the thread-local variables will be created once every time an instance of a class is created, which will cause waste of resources.
Possible problems with thread-specific objects
Data degradation and confusion problems
As shown in the figure above, because TL is a static variable, each new Task() will not reinitialize TL, which will lead to when Thread-A executes Task-2 after executing Task-1, because when executing them The same thread, so the Map object instances obtained by them through TL are the same thread-specific object, which leads to Task-2 may obtain the data operated by Task-1, which may cause data confusion.
So in this case, after we get the object instance of ThreadLocal proxy, we need to do some pre-operation on it, such as clearing the above HashMap object instance.
TL.get().clear();
Many times we also use ThreadLocal to pass some data, for example: storing token and other information in ThreadLocal, but in order to prevent the next task from obtaining this request token information, it needs to be removed in the post-processor of the interceptor This operation is to prevent data confusion!
memory leak problem
Memory leak → means that an object can never be garbage collected by the virtual machine, and it has been occupying a certain piece of memory and cannot be released. An increase in memory leaks results in less and less memory being available, and may even lead to memory overflows.
From the previous ThreadLocal source code analysis, we can see that we store the ThreadLocal object instance and its proxy object in the ThreadLocalMap corresponding to Thread in the form of key-value, and the key-value is really stored in the Entry array of ThreadLocalMap, that is We will encapsulate the key-value into an Entry object.
It can be seen from the above figure that the reference of the Entry to the ThreadLocal instance is a weak reference. When there is no strong reference to other objects, the ThreadLocal instance will be recycled by the virtual machine. At this time, the key in the Entry will be programmed to null, that is, the Entry will change at this time. into an invalid entry.
In addition, the Entry's reference to thread-specific objects is a strong reference, so if the Entry becomes an invalid entry, the thread-specific object will not be recycled due to the strong reference, that is, if the invalid entry will not be used for a long time. If it is cleaned up or never cleaned up, it will occupy the memory for a long time, creating a memory leak phenomenon.
So when are invalid entries cleaned up? In the previous source code analysis of ThreadLocal, the ThreadLocal.set() operation (insertion of ThreadLocalMap) can cause the replacement or cleanup of invalid Entry, but if there is no insertion operation for this thread's ThreadLocalMap, the invalid entry will always occupy memory . So in order to prevent this from happening, we need to develop a good habit of manually calling the ThreadLocal.remove method to clean up invalid entries (usually after the thread ends) after each use of the ThreadLocal object.
- Supplement ↩
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。