Summary about ThreadLocal
Introduction to ThreadLocal class
The ThreadLocal class is used to provide local variables within a thread. When this variable is accessed in a multithreaded environment (through get and set methods), it can ensure that the variables of each thread are relatively independent of those in other threads.
Summary of ThreadLocal:
- Thread Concurrency: in the scenario of multithreading concurrency
- Pass data: we can pass public variables in the same thread and different components through ThreadLocal
- Thread isolation: the variables of each thread are independent and will not affect each other
Internal structure of ThreadLocal
Each Thread maintains a ThreadLocalMap. The key of this Map is the ThreadLocal instance itself, and the value is the real value Object to be stored.
-
There is a Map (ThreadLocalMap) inside each Thread
-
The Map stores the ThreadLocal object (key) and the variable copy (value) of the thread
-
The map inside the Thread is maintained by ThreadLocal, which is responsible for obtaining and setting the variable value of the Thread from the map.
-
For different threads, each time the replica value is obtained, other threads cannot obtain the replica value of the current thread, forming the isolation of replicas without interference with each other.
Benefits of this design:
- After the Thread is destroyed, the corresponding ThreadLocalMap will also be destroyed, which can reduce the use of memory.
About the basic structure of ThreadLocalMap
ThreadLocalMap is an internal class of ThreadLocal. It does not implement the Map interface. It implements the function of Map in an independent way, and its internal Entry is also implemented independently.
Basic structure of ThreadLocalMap
/** * Initial capacity - must be an integral power of 2 */ private static final int INITIAL_CAPACITY = 16; /** * The definitions of table and Entry classes for storing data are analyzed below * Similarly, the array length must be an integral power of 2. */ private Entry[] table; /** * The number of entries in the array can be used to judge whether the current usage of table exceeds the threshold. */ private int size = 0; /** * The threshold for capacity expansion. Capacity expansion is performed when the table usage is greater than it. */ private int threshold; // Default to 0
Similar to HashMap, initial_ Capability represents the initial capacity of the Map; Table is an array of Entry type, which is used to store data; Size represents the number of stores in the table; Threshold represents the corresponding size threshold when capacity expansion is required.
Entry storage structure
/* * Entry Inherit the WeakReference and use ThreadLocal as the key * If the key is null(entry.get() == null), it means that the key is no longer referenced, * Therefore, the entry can also be cleared from the table at this time. (recycled by garbage collector) */ static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; // In ThreadLocalMap, Entry is also used to save K-V structure data. However, the key in the Entry can only be a ThreadLocal object Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
About memory leaks
Memory leak concept:
-
Memory overflow: memory overflow. There is not enough memory for the applicant.
-
Memory leak: memory leak refers to the heap memory that has been dynamically allocated in the program. For some reason, the program does not release or cannot release, resulting in a waste of system memory, slowing down the running speed of the program and even system crash. The accumulation of memory leaks will eventually lead to memory overflow.
About four reference types
Strong reference: as long as the strong reference exists, the garbage collector will never recycle the referenced object. Even when the memory is insufficient, the JVM will directly throw OutOfMemoryError and will not recycle it. If you want to break the connection between the strong reference and the object, you can assign the strong reference to null.
Soft reference: soft reference is used to describe some unnecessary but still useful objects. When the memory is sufficient, the soft reference object will not be recycled. Only when the memory is insufficient, the system will recycle the soft reference object. If there is still not enough memory after recycling the soft reference object, a memory overflow exception will be thrown.
Weak reference: the reference strength of weak reference is weaker than that of soft reference. No matter whether the memory is sufficient or not, as long as the JVM starts garbage collection, those objects associated with weak reference will be recycled.
Virtual reference: virtual reference is the weakest reference relationship. If an object only holds virtual references, it may be recycled at any time as if it does not have any references
True cause of memory leak
The root cause of ThreadLocal memory leak is: since the life cycle of ThreadLocalMap is as long as Thread, memory leak will occur if the corresponding key is not manually deleted.
Why do Entry storage structures use weak references
No matter what type of reference the key in ThreadLocalMap uses, memory leakage cannot be completely avoided, which has nothing to do with the use of weak references.
There are two ways to avoid memory leaks:
- After using ThreadLocal, call its remove method to delete the corresponding Entry
- After using ThreadLocal, the current Thread will end
In fact, in the set/getEntry method in ThreadLocalMap, it will judge that the key is null (that is, ThreadLocal is null). If it is null, it will set the value to null.
This means that when ThreadLocal is used and CurrentThread is still running, even if you forget to call the remove method, the weak reference can provide more protection than the strong use: the ThreadLocal of the weak reference will be recycled, and the corresponding value will be cleared the next time ThreadLocalMap calls any of the methods set, get and remove, so as to avoid memory leakage.
Construction method of ThreadLocalMap
/* * firstKey : This ThreadLocal instance (this) * firstValue : Thread local variables to save */ ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { //Initialize table table = new ThreadLocal.ThreadLocalMap.Entry[INITIAL_CAPACITY]; //Calculation index (key code) int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); //Set value table[i] = new ThreadLocal.ThreadLocalMap.Entry(firstKey, firstValue); size = 1; //Set threshold threshold = len * 2 / 3 setThreshold(INITIAL_CAPACITY); } private void setThreshold(int len) { threshold = len * 2 / 3; }
The constructor first creates an Entry array with a length of 16, then calculates the index corresponding to the firstKey, stores it in table, and sets size and threshold.
Analyze and calculate the index firstkey.threadlocalhashcode & (initial_capability - 1)
About firstKey.threadLocalHashCode
private final int threadLocalHashCode = nextHashCode(); //AtomicInteger is an Integer class that provides atomic operations. It operates addition and subtraction in a thread safe manner. It is suitable for high concurrency private static AtomicInteger nextHashCode = new AtomicInteger(); //Special hash value private static final int HASH_INCREMENT = 0x61c88647; private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); }
An AtomicInteger type is defined here. Each time the current value is obtained and added with HASH_INCREMENT, HASH_INCREMENT = 0x61c88647. This value is related to the Fibonacci sequence (golden section number). Its main purpose is to make the hash code evenly distributed in the n-power array of 2, that is, the Entry[] table. This can avoid hash conflicts as much as possible.
About & (initial_capability - 1)
The hashcode & (size - 1) algorithm is used to calculate the hash, which is equivalent to a more efficient implementation of the modular operation hashcode% size. Because of this algorithm, we require that the size must be an integral power of 2, which can also reduce the number of hash conflicts on the premise that the index does not cross the boundary.
set method in ThreadLocalMap
private void set(ThreadLocal<?> key, Object value) { ThreadLocal.ThreadLocalMap.Entry[] tab = table; int len = tab.length; //Calculate index (key code, just analyzed) int i = key.threadLocalHashCode & (len-1); /** * Find elements using linear probing (key codes) */ for (ThreadLocal.ThreadLocalMap.Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); //The key corresponding to ThreadLocal exists and directly overwrites the previous value if (k == key) { e.value = value; return; } // The key is null, but the value is not null, indicating that the previous ThreadLocal object has been recycled, // The Entry in the current array is a stale element if (k == null) { //Replace old elements with new elements. This method performs many garbage cleaning actions to prevent memory leakage replaceStaleEntry(key, value, i); return; } } //If the key corresponding to ThreadLocal does not exist and no old element is found, a new Entry is created at the position of the empty element. tab[i] = new Entry(key, value); int sz = ++size; /** * cleanSomeSlots Used to clear elements with e.get()==null, * The object associated with this data key has been recycled, so the Entry(table[index]) can be set to null. * If no entry is cleared and the current usage reaches the load factor definition (2 / 3 of the length), proceed to * rehash (perform a full table scan cleanup) */ if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); } /** * Gets the next index of the ring array */ private static int nextIndex(int i, int len) { return ((i + 1 < len) ? i + 1 : 0); }
Code execution process:
-
First, calculate the index I according to the key, and then find the Entry at position i,
-
If the Entry already exists and the key is equal to the passed in key, then directly assign a new value to the Entry,
-
If the Entry exists but the key is null, call replacestateentry to replace the Entry with an empty key,
-
Continue loop detection until a null place is encountered. At this time, if you have not return ed during the loop, create an Entry at the null position, insert it, and increase the size by 1.
-
Finally, we call cleanSomeSlots to clean up key's Entry for null, and finally return to clean up Entry. Then we'll decide if sz is > thresgold = rehash, and then we will call the rehash function to perform a sweep scan of the whole table.
Key analysis: ThreadLocalMap uses linear detection to solve hash conflicts.
This method detects the next address at a time until there is an empty address. If the empty address cannot be found in the whole space, overflow will be generated.
For example, suppose the length of the current table is 16, that is, if the hash value of the calculated key is 14, if there is already a value on table[14] and its key is inconsistent with the current key, then a hash conflict occurs. At this time, 14 plus 1 will get 15, and take table[15] for judgment. At this time, if there is still a conflict, it will return to 0, take table[0], and so on until it can be inserted.
According to the above description, the Entry[] table can be regarded as a ring array.
☀️ Finally, put a picture to better understand:
If no free address is found, an overflow occurs.
For example, suppose the length of the current table is 16, that is, if the hash value of the calculated key is 14, if there is already a value on table[14] and its key is inconsistent with the current key, then a hash conflict occurs. At this time, 14 plus 1 will get 15, and take table[15] for judgment. At this time, if there is still a conflict, it will return to 0, take table[0], and so on until it can be inserted.
According to the above description, the Entry[] table can be regarded as a ring array.
☀️ Finally, put a picture to better understand: