1, Function description
ThreadLocal solves the blocking problem of accessing shared variables, and does not need to sacrifice CPU resources like CAS operation. It maintains a variable copy for each thread. When accessing the variables in ThreadLocal, each thread actually accesses the variable copy in its own thread, and the variable copy in this thread is isolated from the variable copy of other threads, They don't affect each other. That is, the variables wrapped in ThreadLocal are thread level variables.
2, Source code interpretation
ThreadLocal saves data through an internal class ThreadLocalMap, takes itself as a key, and starts with the get method.
public T get() { // Get current thread Thread t = Thread.currentThread(); // Gets the ThreadLocalMap in the current thread ThreadLocalMap map = getMap(t); if (map != null) { // Use threadlocal as the key to obtain the Entry object in ThreadLocalMap ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") // Gets a copy of the value of the threadlocal package within the thread T result = (T)e.value; return result; } } return setInitialValue(); }
It is found in the get method that Threadlocal first obtains the current thread, and then uses the current thread as the key to obtain the ThreadLocalMap object. Then the ThreadLocalMap object is only visible to the current thread, and the contents contained in ThreadLocalMap are only visible to the current thread. Check the getMap method:
ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
At this time, it is found that ThreadLocalMap is actually saved in the threadLocals variable of Thread class. Check the Thread class code. ThreadLocalMap class variable threadLocals is saved internally, that is, ThreadLocalMap is defined in ThreadLocal class, but actually saved in Thread class.
/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null;
Observe the definition of the ThreadLocalMap class
static class ThreadLocalMap { // Static internal class, saving key value pairs, and using weak references static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } }
The main reason for using weak references is to help the jvm with garbage collection (see WeakHashMap)
It can be found from the memory reference diagram that there are two references to ThreadLocal, namely, the user-defined ThreadLocal variable and the key in the Entry. If the key in the Entry holds a strong reference to ThreadLocal, even if the user-defined ThreadLocal variable is set to null, the memory occupied by ThreadLocal cannot be recycled due to the existence of the key, It will cause memory leakage.
The key in the Entry holds a weak reference to ThreadLocal. When the custom ThreadLocal variable is set to null, only the key references ThreadLocal in the java heap. Because of the characteristics of weak reference, if there is no other strong reference connection, it can be recycled, so it will not cause memory leakage.
If you set ThreadLocal to null to help GC, it is found that the ThreadLocal variable can be recycled, but if value is not cleared before, value will always hold a reference, which will cause memory leakage. Therefore, when the ThreadLocal in a thread is used up, you must first call the remove method to clear the value and set the ThreadLocal to null.
After understanding the role of weak reference, continue to look at the getEntry method of ThreadLocalMap class
private Entry getEntry(ThreadLocal<?> key) { // Obtain the subscript of the Entry in the table through the hashcode of the threadlocal class int i = key.threadLocalHashCode & (table.length - 1); // Get Entry object in table Entry e = table[i]; if (e != null && e.get() == key) return e; else return getEntryAfterMiss(key, i, e); }
Through the above code, it is found that the actual storage structure of ThreadLocalMap is Entry[] table, and the structure of table is hash table. To verify this view, continue to check the set method of ThreadLocalMap class
private void set(ThreadLocal<?> key, Object value) { // Get Entry table Entry[] tab = table; // Get table length int len = tab.length; // Obtain the subscript position of the Entry in the table through the hashcode of the threadlocal class int i = key.threadLocalHashCode & (len-1); // If the Entry[i] is not empty, traverse the table from the subscript I for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); // threallocal is the same as the key in the entry and directly replaces the value if (k == key) { e.value = value; return; } // If key is null, set key,value and modify hashcode if (k == null) { replaceStaleEntry(key, value, i); return; } } // The Entry[i] is empty. Set the key and value directly tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }
After reading the set method, you have a general grasp of the table structure.
When setting the value, obtain the subscript position to be stored through the hash code of threadlocal and the length of table. The same method is used to obtain the value.