1, What is ThreadLocal and what is it used for?
ThreadLocal is essentially a proxy tool class. In ThreadLocal class, there is a static internal class ThreadLocalMap. Through the set(), get() method encapsulated by ThreadLocal, you can access the data in ThreadLocalMap. The Key of ThreadLocalMap is related to this (i.e. ThreadLocal itself), Each Thread maintains its own ThreadLocalMap, that is, threadLocals. Therefore, through ThreadLocal data access, it actually operates on the Thread's own threadLocals, and the threadLocals of each Thread are independent. Therefore, this part of data is isolated from other threads, which can prevent its own variables from being modified by other threads.
2, ThreadLocal source code
The pseudocode for simple use is as follows:
ThreadLocal<String> threadlocal = new ThreadLocal(); threadlocal.set("test"); String name = threadlocal.get(); threadlocal.remove();
set method of ThreadLocal
public void set(T value) { // Get current thread Thread t = Thread.currentThread(); // Gets the ThreadLocalMap of the current thread ThreadLocalMap map = getMap(t); // Is the map empty if (map != null) { // Stored value map.set(this, value); } else { // Create a new map and assign values createMap(t, value); } }
As can be seen from the following getMap and createMap methods, each new thread will instantiate a ThreadLocalMap and assign it to the member variable threadLocals. If threadLocals is not empty, the existing ThreadLocalMap will be used directly.
ThreadLocalMap getMap(Thread t) { // Return threadLocals of Thread return t.threadLocals; } void createMap(Thread t, T firstValue) { // Call the ThreadLocalMap construction method and assign it to threadLocals t.threadLocals = new ThreadLocalMap(this, firstValue); } ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { // private static final int INITIAL_CAPACITY = 16; Initial capacity 16 table = new Entry[INITIAL_CAPACITY]; // index calculates that the firstKey is this; int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); table[i] = new Entry(firstKey, firstValue); size = 1; setThreshold(INITIAL_CAPACITY); }
get method of ThreadLocal
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } // map null returned null return setInitialValue(); }
private T setInitialValue() { // protected T initialValue() { return null; } --> value = null T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { map.set(this, value); } else { createMap(t, value); } if (this instanceof TerminatingThreadLocal) { TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this); } return value; }
remove method of ThreadLocal
// Remove calls the remove method of ThreadLocalMap public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) { m.remove(this); } }
3, ThreadLocalMap underlying structure
// The Entry inherits the WeakReference. The next GC will be recycled static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } // The initialization capacity is 16, which must be to the power of 2 private static final int INITIAL_CAPACITY = 16; // Entry array private Entry[] table; // Number of actual elements of the array private int size = 0; // Capacity expansion threshold, len * 2 / 3 Entry array length * 2 / 3 threshold 2 / 3 private int threshold; // Default to 0
As can be seen from the above code, ThreadLocalMap is very similar to HashMap, but there are great differences. It is similar to the array, the initialization capacity is 16, and there is also capacity expansion (the threshold is 2 / 3). The difference is that ThreadLocalMap has only one Entry array, and the index passes the key Threadlocalhashcode & (len-1) hashcode is obtained by length bit operation. What if the hash conflicts? HashMap has linked lists and red black trees, while ThreadLocalMap only has an Entry array.
So let's look at the set method of ThreadLocalMap
private void set(ThreadLocal<?> key, Object value) { // We don't use a fast path as with get() because it is at // least as common to use set() to create new entries as // it is to replace existing ones, in which case, a fast // path would fail more often than not. Entry[] tab = table; int len = tab.length; // Calculate index int i = key.threadLocalHashCode & (len-1); // Start from tab[i] and cycle back to find the empty position for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); // The key is equal and the value is overwritten if (k == key) { e.value = value; return; } // If the key is null, initialize an Entry if (k == null) { replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); int sz = ++size; // The capacity expansion threshold is 2 / 3 if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); } // i+1 private static int nextIndex(int i, int len) { return ((i + 1 < len) ? i + 1 : 0); }
From the above code, we can see that the method of ThreadLocalMap to solve hash conflict is from tab[i], int i = key threadLocalHashCode & (len-1); Start the loop to find the next empty tab[i] position, terminate the loop when it is found, and store the value in the empty tab[i] position.
private Entry getEntry(ThreadLocal<?> key) { int i = key.threadLocalHashCode & (table.length - 1); Entry e = table[i]; // Return if the key s are equal if (e != null && e.get() == key) return e; else // The current position key s are not equal. Continue to search return getEntryAfterMiss(key, i, e); } private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) { Entry[] tab = table; int len = tab.length; while (e != null) { ThreadLocal<?> k = e.get(); if (k == key) return e; if (k == null) expungeStaleEntry(i); else // See the familiar nextIndex again i = nextIndex(i, len); e = tab[i]; } return null; }
In addition, you can see the remove method, rehash method and resize method. It feels relatively simple. Here, post the source code of resize method. The load factor is 2 / 3, and the expansion is twice the original length.
private void resize() { Entry[] oldTab = table; int oldLen = oldTab.length; int newLen = oldLen * 2; Entry[] newTab = new Entry[newLen]; int count = 0; for (Entry e : oldTab) { if (e != null) { ThreadLocal<?> k = e.get(); if (k == null) { e.value = null; // Help the GC } else { int h = k.threadLocalHashCode & (newLen - 1); while (newTab[h] != null) h = nextIndex(h, newLen); newTab[h] = e; count++; } } } setThreshold(newLen); size = count; table = newTab; }
4, InheritableThreadLocal
In addition to threadlocales, inheritablethreadlocales is also ThreadLocal Threadlocalmap type, what is it used for? -- > Let the child Thread access the local variable value of the parent Thread.
public class Thread implements Runnable { ...... // Thread. Java 448 lines if (inheritThreadLocals && parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); }
public class InheritableThreadLocal<T> extends ThreadLocal<T> { protected T childValue(T parentValue) { return parentValue; } ThreadLocalMap getMap(Thread t) { return t.inheritableThreadLocals; } void createMap(Thread t, T firstValue) { t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue); } }
When the parent thread creates a child thread, the constructor in ThreadLocalMap will copy the variables in the inheritableThreadLocals of the parent thread to the inheritableThreadLocals variable of the child thread.
Pseudo code
private void ThreadLocalTest () { final ThreadLocal threadLocal = new InheritableThreadLocal(); threadLocal.set("test"); // Child thread Thread t = new Thread() { @Override public void run() { super.run(); System.out.print(threadLocal.get()); } }; t.start(); }
5, Memory leak
Review the Entry array structure of ThreadLocalMap. The Entry inherits the WeakReference, so the key in ThreadLocalMap is the weak reference of ThreadLocal.
// The Entry inherits the WeakReference. The next GC will be recycled static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
You can also learn about the description of weak references, other strong references, soft references and virtual references in in-depth understanding of Java virtual machine. On page 65 of the second edition of in-depth understanding of Java virtual machine, the relevant knowledge of JVM is.
Summary: when there is no dependency on ThreadLoca elsewhere, the ThreadLocal object in ThreadLocalMap will be recycled in the next garbage collection, but the corresponding value will not be recycled. At this time, there may be items with null key but non null value in the Map. Therefore, after the actual ThreadLocal is used, it is necessary to call the remove method in time to avoid memory leakage.
Pseudo code
ThreadLocal<String> threadlocal = new ThreadLocal(); try{ threadlocal.set("test"); String str = threadlocal.get(); } finally { threadlocal.remove(); }
reference resources
Detailed explanation of ThreadLocal in Java
The interviewer didn't expect me to use ThreadLocal so quickly that people would be stupid