1. What is ThreadLocal?Use scenarios
Introduction to ThreadLocal
ThreadLocal is a thread-local variable that provides a solution to multithreaded concurrency problems. When using ThreadLocal to maintain variables, ThreadLocal provides a separate copy of variables for each thread that uses the variable, so each thread can independently change its own copy without affecting the corresponding copy of other threads.
ThreadLocal usage scenarios
When multiple threads try to get a shared variable, they need to get a copy of its initial value.Each thread stores a copy of the variable, and changes to the copy of the variable do not affect the variable itself.For scenarios where multiple threads depend on different variable values to complete operations.For example:
- Switching of Multiple Data Sources
- spring declarative transaction
2. Use cases for ThreadLocal
ThreadLocal class interface:
- void set(T value): Sets the value of the thread local variable for the current thread
- T get(): Gets the thread local variable for the current thread
- void remove(): Deletes the value of the current thread local variable in order to reduce memory consumption
- T initialValue(): The initial value of the thread's local variable (the default is null), which is a lazy load method of protected that is executed when the thread first calls get() or set(T value), and is designed to override subclasses.
public class ThreadLocalDemo { private static ThreadLocal<Index> index = new ThreadLocal(){ @Override protected Object initialValue() { return new Index(); } }; private static class Index{ private int num; public void incr(){ num++; } } public static void main(String[] args) { for(int i=0; i<5; i++){ new Thread(() ->{ Index local = index.get(); local.incr(); System.out.println(Thread.currentThread().getName() + " " + index.get().num); }, "thread_" + i).start(); } } }
Output results:
thread_1 1 thread_0 1 thread_3 1 thread_4 1 thread_2 1
You can see that each thread gets an initial value of 0 and has no effect on the operation of num++.
3. How ThreadLocal is implemented
3.1 ThreadLocal data structure
ThreadLocal maintains a Map-like ThreadLocalMap data structure, and each Thread class has a ThreadLocalMap member variable.ThreadLocalMap takes a ThreadLocal variable as the key and a copy of the ThreadLocal variable as the value, as shown in the diagram:
It is important to note that the relationship between Entry's key and value in ThreadLocal is maintained systematically and that improper maintenance can lead to insecurity in a multithreaded state (generally not, at least with caution).
3.2 get() source code analysis
public T get() { //Get Current Thread Thread t = Thread.currentThread(); //Gets the ThreadLocalMap of the current thread ThreadLocalMap map = getMap(t); if (map != null) { //If the ThreadLocalMap has already been created, get the value by using the current threadLocal object as the key ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } //If the ThreadLocalMap has not been created or cannot be found in the ThreadLocalMap return setInitialValue(); }
3.2.1 ThreadLocalMap not initialized
ThreadLocalMap is not initialized and when ThreadLocalMap is null, the setInitialValue() method is called:
private T setInitialValue() { //The initialValue method is usually overridden, and if not, returns null directly T value = initialValue(); Thread t = Thread.currentThread(); //Gets the ThreadLocalMap of the current thread ThreadLocalMap map = getMap(t); if (map != null) //ThreadLocalMap has already been created, so set the initial value directly (that is, save a copy of the variable) from the initialValue method map.set(this, value); else //Create ThreadLocalMap createMap(t, value); return value; }
The initialValue() method is overridden by us, and it is important to note that the return value must be a new object, not a direct return of an object reference.Because if multiple threads hold a copy of the same reference, they modify the value of the shared variable through that reference, which affects each other.Our original purpose was to get a copy of the initial value of the shared variable, and each thread's modifications to the copy did not affect the variable itself.
Let's see how createMap creates a threadLocalMap
void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { //Create an Entry array with an initial capacity of 16 table = new Entry[INITIAL_CAPACITY]; //Position in the array by threadLocal threadLocalHashCode int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); //Save in Array table[i] = new Entry(firstKey, firstValue); //Record size used size = 1; //Set threshold to 2/3 of capacity setThreshold(INITIAL_CAPACITY); }
3.2.2 Initialize threadLocalMap
After initializing threadLocalMap, the thread calls the get() method again, and what else does it do?
public T get() { //Get Current Thread Thread t = Thread.currentThread(); //Gets the ThreadLocalMap of the current thread ThreadLocalMap map = getMap(t); if (map != null) { //If the ThreadLocalMap has already been created, get the value by using the current threadLocal object as the key ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } //If the ThreadLocalMap has not been created or cannot be found in the ThreadLocalMap return setInitialValue(); }
You can see that elements are found by map.getEntry(this)
private Entry getEntry(ThreadLocal<?> key) { int i = key.threadLocalHashCode & (table.length - 1); Entry e = table[i]; if (e != null && e.get() == key) return e; else //If the key of the positioned element is not equal to the key passed in, look back 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) //Clear out expired key s and move back elements (moved past positions) forward expungeStaleEntry(i); else //Move one bit backward i = nextIndex(i, len); e = tab[i]; } return null; }
private static int nextIndex(int i, int len) { return ((i + 1 < len) ? i + 1 : 0); }
private int expungeStaleEntry(int staleSlot) { Entry[] tab = table; int len = tab.length; // Clear current element tab[staleSlot].value = null; tab[staleSlot] = null; size--; //Move the element behind this element forward because the hash conflict moved through the position Entry e; int i; for (i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) { ThreadLocal<?> k = e.get(); if (k == null) { e.value = null; tab[i] = null; size--; } else { int h = k.threadLocalHashCode & (len - 1); //H!= I indicates a hash conflict if (h != i) { tab[i] = null; // Unlike Knuth 6.4 Algorithm R, we must scan until // null because multiple entries could have been stale. while (tab[h] != null) h = nextIndex(h, len); tab[h] = e; } } } return i; }
What if map.getEntry(this) also can't find the element?
public T get() { //Get Current Thread Thread t = Thread.currentThread(); //Gets the ThreadLocalMap of the current thread ThreadLocalMap map = getMap(t); if (map != null) { //If the ThreadLocalMap has already been created, get the value by using the current threadLocal object as the key ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } //If the ThreadLocalMap has not been created or cannot be found in the ThreadLocalMap return setInitialValue(); }
Then continue to call the setInitialValue() method
private T setInitialValue() { //The initialValue method is usually overridden, and if not, returns null directly T value = initialValue(); Thread t = Thread.currentThread(); //Gets the ThreadLocalMap of the current thread ThreadLocalMap map = getMap(t); if (map != null) //ThreadLocalMap has already been created, so set the initial value directly (that is, save a copy of the variable) from the initialValue method map.set(this, value); else //Create ThreadLocalMap createMap(t, value); return value; }
You can see that the map.set(this, value) method inside it will be called
private void set(ThreadLocal<?> key, Object value) { 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)]) { ThreadLocal<?> k = e.get(); if (k == key) { e.value = value; return; } if (k == null) { //Replace expired elements and clear some of the later ones replaceStaleEntry(key, value, i); return; } } //If you can't find one in the table, create a new one tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) //Expand and and re-hash if no elements are cleared beyond the threshold rehash(); }
3.3 set() source code analysis
See how set() handles things like setInitialValue, with fewer initialization values and the same everything else. Refer to get() where [ThreadLocalMap is not initialized] (#3.2.2 initializes threadLocalMap):
public void set(T value) { //Get Current Thread Thread t = Thread.currentThread(); //Gets the ThreadLocalMap of the current thread ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
3.4 Summary
- First determine if the threadLocalMap for the current thread exists, or create one if it does not.Save ThreadLocal as key and a copy of the initial value of the shared variable as value in threadLocalMap
- If threadLocalMap exists, use ThreadLocal as the key and hash the location in the array to find the value
- If ThreadLocal is not found in the threadLocalMap (reason: deleted, or this ThreaLocal is not joined to the threadLocalMap, since threadLocalMap can hold multiple threadLocals), save ThreadLocal as key, a copy of the shared variable initial value as value in the threadLocalMap
- During the set process, if there are other elements (hash conflicts) at the current location, look back until no other elements exist.During the set process, some out-of-date (null key) elements are cleared.Finally, depending on the size of the size of the size, whether to expand or not, the hash will be relocated
Previous articles:
- java multi-threaded concurrent series--basic knowledge points (required for written tests, interviews)
- Enhance Competency, Wait for a Pay Rise - Java Concurrent AQS Full Detail
- Promote Your Competency, Wait for a Pay Rise - Synchronized for Java Concurrent
- ...
Are you all OK?If you like, point and focus with your finger!!Thank you for your support!
Welcome to Search, original technical articles first launched