This article discusses ThreadLocal in JDK 1.8
(If you want to learn programming by yourself, please search Circle T community More industry-related information and more industry-related free video tutorials. It's totally free!
ThreadLocal concept
ThreadLocal multi-threaded concurrent access to variables solution, for each thread to provide a copy of variables, space for time.
- ThreadLocal creates a copy of this variable in each thread, that is, there will be one variable in each thread, and it can be used anywhere in the thread. ThreadLocal does not affect each other and implements thread isolation, so there is no thread security problem and it will not seriously affect the performance of program execution.
- Since replicas are created in each thread, it is important to consider resource consumption, such as memory usage that is larger than that without ThreadLocal.
- If you use ThreadLocal, it is usually defined as the private static type, and in my opinion, it is better to define the private static final type.
ThreadLocal usage scenario
Personally, I think we can consider using ThreadLocal as long as we meet the following two requirements
- Each thread needs its own separate instance
- Instances need to be shared in multiple methods, but do not want to be shared by multiple threads
For example: create a database connection, in the case of multi-threading, we certainly do not want to have thread A get the connection not finished, thread B will close it or multiple threads share a connection, resulting in data manipulation confusion and so on. And our correct posture should have the following similar codes:
private static ThreadLocal<Connection> connTl = new ThreadLocal<>(); public static Connection getConnection() throws SQLException{ Connection conn = connTl.get(); if(conn==null){ conn = dataSource.getConnection(); connTl.set(conn); } return conn; }
Introduction of ThreadLocal Common Methods
class ThreadLocal<T> { T get(); void set(T value); void remove(); }
Set the value of the thread local variable for the current thread
public void set(T value);
Returns the thread local variable corresponding to the current thread
public T get();
Delete the value of the thread's current thread local variable
public void remove()
ThreadLocal source code parsing
Before looking at the source code for common methods, let's look at ThreadLocalMap
ThreadLocalMap is a static class within ThreadLocal
static class ThreadLocalMap { static class Entry extends WeakReference<ThreadLocal<?>> { Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } /** * Initial capacity */ private static final int INITIAL_CAPACITY = 16; /** * Entity table */ private Entry[] table; /** * Table Initial Size */ private int size = 0; /** * When size reaches threashold, it needs to resize the whole Map. The initial value of threshold is len * 2/3. */ private int threshold; // Default to 0 /** * Set the adjustment size threshold to maintain 2/3 of the load factor in the worst case. */ private void setThreshold(int len) { threshold = len * 2 / 3; } /** * Gets the next index, returns 0 beyond length */ private static int nextIndex(int i, int len) { return ((i + 1 < len) ? i + 1 : 0); } /** * Returns the last index, if - 1 is negative, the index of length - 1 */ private static int prevIndex(int i, int len) { return ((i - 1 >= 0) ? i - 1 : len - 1); } /** * Construct parameters to create a ThreadLocalMap code * ThreadLocal For key, our generic is value */ ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { table = new Entry[INITIAL_CAPACITY]; int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); table[i] = new Entry(firstKey, firstValue); size = 1; setThreshold(INITIAL_CAPACITY); } /** * ThreadLocal It's thread-isolated, so it's reasonable that there won't be any behavior of data sharing and transmission. * This is Inheritable ThreadLocal, which provides a mechanism for data sharing between father and son. */ private ThreadLocalMap(ThreadLocalMap parentMap) { Entry[] parentTable = parentMap.table; int len = parentTable.length; setThreshold(len); table = new Entry[len]; for (int j = 0; j < len; j++) { Entry e = parentTable[j]; if (e != null) { @SuppressWarnings("unchecked") ThreadLocal<Object> key = (ThreadLocal<Object>) e.get(); if (key != null) { Object value = key.childValue(e.value); Entry c = new Entry(key, value); int h = key.threadLocalHashCode & (len - 1); while (table[h] != null) h = nextIndex(h, len); table[h] = c; size++; } } } } /** * Get the index location of ThreadLocal and get the content through subscript index */ private Entry getEntry(ThreadLocal<?> key) { // Subscription determination by hashcode int i = key.threadLocalHashCode & (table.length - 1); Entry e = table[i]; // If found, return directly if (e != null && e.get() == key) return e; else // If you can't find it, then you start traversing backwards from position i. Based on the linear detection method, it is possible to find it after position i. return getEntryAfterMiss(key, i, e); } private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) { Entry[] tab = table; int len = tab.length; // Cyclic backward traversal while (e != null) { // Get the k corresponding to the node ThreadLocal<?> k = e.get(); // Equality returns if (k == key) return e; // If null, trigger a continuous segment cleanup if (k == null) expungeStaleEntry(i); // Get the next subscript and judge else i = nextIndex(i, len); e = tab[i]; } return null; } /** * ThreadLocalMap Through this method, we can see that the hash table is a linear detection method to solve conflicts. */ private void set(ThreadLocal<?> key, Object value) { // Open a new reference to table Entry[] tab = table; // Get the length of the table int len = tab.length; // Get the subscript corresponding to ThreadLocal in the table int i = key.threadLocalHashCode & (len-1); // Cyclic traversal begins with this subscript for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); // If the same key is encountered, the value is replaced directly. if (k == key) { e.value = value; return; } // If the key has been reclaimed, replace the invalid key if (k == null) { replaceStaleEntry(key, value, i); return; } } // Find the empty location, create the Entry object and insert it tab[i] = new Entry(key, value); // The element size in the table increases by itself int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); } /** * Remove key method */ private void remove(ThreadLocal<?> key) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { if (e.get() == key) { e.clear(); expungeStaleEntry(i); return; } } } private void replaceStaleEntry(ThreadLocal<?> key, Object value, int staleSlot) { // Create a reference variable to point to the table Entry[] tab = table; // Get table length int len = tab.length; Entry e; // Recording Node Subscription for Current Failure int slotToExpunge = staleSlot; /** * Starting with the staleSlot subscript, scan forward to find and record the subscript whose front value is null. */ for (int i = prevIndex(staleSlot, len); (e = tab[i]) != null; i = prevIndex(i, len)) if (e.get() == null) slotToExpunge = i; /** * Scanning backwards from the staleSlot subscript */ for (int i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) { // Get the ThreadLocal object corresponding to the Entry node ThreadLocal<?> k = e.get(); /** * If it corresponds to the new key, assign value directly and replace the two subscripts i and staleSlot */ if (k == key) { e.value = value; tab[i] = tab[staleSlot]; tab[staleSlot] = e; if (slotToExpunge == staleSlot) slotToExpunge = i; cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); return; } /* If the current subscript is invalid and no invalid Entry node is found in the backward scan process, slotToExpunge assigns the current location.*/ if (k == null && slotToExpunge == staleSlot) slotToExpunge = i; } // If the key is not found in the table, the new Entry is located directly in the current location. tab[staleSlot].value = null; tab[staleSlot] = new Entry(key, value); if (slotToExpunge != staleSlot) cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); } /** * The core cleanup function, the main thing it does is * 1,Starting with staleSlot, traverse backwards to set null for the value and Entry nodes of the Entry node where the ThreadLocal object is reclaimed, which is convenient for GC, and size decreases by 1 * 2,The Entry node that is not null will be rehash ed, so long as it is not in the current location, it will move Entry to the next null location. * So it's actually a sequential cleanup and rehash operation starting with staleSlot. */ private int expungeStaleEntry(int staleSlot) { Entry[] tab = table; //Get length int len = tab.length; // The subscript that will be passed in is null tab[staleSlot].value = null; tab[staleSlot] = null; size--; Entry e; int i; //Traversal deletes all subsequent nodes of the specified node, and ThreadLocal is recycled for (i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) { //Get the key in entry ThreadLocal<?> k = e.get(); // If ThreadLocal is null, then null is set to the value and the position of array subscripts to facilitate GC and size-1. if (k == null) { e.value = null; tab[i] = null; size--; } else { //If not null //Recalculate the subscript of key int h = k.threadLocalHashCode & (len - 1); // If it's the current location, go through the next one // If it's not the current position, start with i again and find the next coordinate to assign null. if (h != i) { tab[i] = null; while (tab[h] != null) h = nextIndex(h, len); tab[h] = e; } } } return i; } /** * Clean up recycled Entry */ private boolean cleanSomeSlots(int i, int n) { boolean removed = false; Entry[] tab = table; int len = tab.length; do { i = nextIndex(i, len); Entry e = tab[i]; // The Entry object is not empty, but if the ThreadLocal key is null, it is cleared if (e != null && e.get() == null) { n = len; removed = true; // Call cleanup function i = expungeStaleEntry(i); } } while ( (n >>>= 1) != 0); return removed; } /** * rehash operation */ private void rehash() { expungeStaleEntries(); // Use lower threshold for doubling to avoid hysteresis if (size >= threshold - threshold / 4) resize(); } /** * Expansion of tables, because to ensure that the length of tables is a power of 2, the expansion will be doubled. */ private void resize() { Entry[] oldTab = table; //Old table length int oldLen = oldTab.length; //New table length int newLen = oldLen * 2; Entry[] newTab = new Entry[newLen]; int count = 0; /** * Starting with subscript 0, iterate backwards one by one to insert into the new table * 1,If the key is already null, value sets null to facilitate GC recycling * 2,Calculate subscripts by hashcode & len - 1, and insert backward detection by linear detection if the location already has an Entry array */ for (int j = 0; j < oldLen; ++j) { Entry e = oldTab[j]; 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++; } } } //Resetting the Threshold of Capacity Expansion setThreshold(newLen); //Update size size = count; //Point to the new Entry array table = newTab; } /** * Clear all useless entries in the table */ private void expungeStaleEntries() { Entry[] tab = table; int len = tab.length; for (int j = 0; j < len; j++) { Entry e = tab[j]; if (e != null && e.get() == null) expungeStaleEntry(j); } } }
get method
public T get() { // Get the current Thread object Thread t = Thread.currentThread(); // Getting ThreadLocal Map in Thread through getMap ThreadLocalMap map = getMap(t); if (map != null) { // If the map already exists, take the current ThreadLocal as the key, get the Entry object, and fetch the value from the Entry ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } // If the map is empty, the setInitialValue is called for initialization return setInitialValue(); }
getMap method
ThreadLocalMap getMap(Thread t) { //Return ThreadLocalMap in Thread return t.threadLocals; } //Thread.java class threadLocals property ThreadLocal.ThreadLocalMap threadLocals = null;
The ThreadLocalMap reference actually exists in the ThreadLocal class
Entry entity
//Entry is a key-value structure with key as ThreadLocal and value as stored values static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
setInitialValue method
setInitialValue is called when Map does not exist
private T setInitialValue() { //Invoke initialValue to generate an initial value, go deep into the initialValue function, and we know that it returns a null. T value = initialValue(); Thread t = Thread.currentThread(); //Get ThreadLocalMap ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else //If it does not exist, it calls createMap to create ThreadLocalMap createMap(t, value); return value; } void createMap(Thread t, T firstValue) { //new a ThreadLocalMap object goes in t.threadLocals = new ThreadLocalMap(this, firstValue); }
set method
public void set(T value) { //Get the current thread Thread t = Thread.currentThread(); //Get map ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
map.set(this,value) method
private void set(ThreadLocal<?> key, Object value) { Entry[] tab = table; int len = tab.length; //Calculate the position according to the key int i = key.threadLocalHashCode & (len-1); //Cycle detection for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); //If Entry already exists and the key is equal to the incoming key, then a new value is assigned directly to the Entry. if (k == key) { e.value = value; return; } //If Entry exists, but the key is null, the replaceStaleEntry is called to replace the Entry whose key is empty. if (k == null) { replaceStaleEntry(key, value, i); return; } } //Create an entry tab[i] = new Entry(key, value); //sz plus 1 int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }
remove method
public void remove() { //Get map ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) //remove calling map m.remove(this); }
ThreadLocalMap.remove(this) method
private void remove(ThreadLocal<?> key) { Entry[] tab = table; int len = tab.length; //Get the index location int i = key.threadLocalHashCode & (len-1); //Loop traversal table table table table for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { //If found, the call clears the method and ends the loop if (e.get() == key) { //Call clear() of weakrefrence to clear references e.clear(); //Continuous Segment Clearance expungeStaleEntry(i); return; } } }