preface
Before we start, take a look at the code below:
ThreadLocal<String> stringThreadLocal = new ThreadLocal<>(); New Thread(() -> {stringThreadLocal.set(" test "); System.out.println(" child thread fetch: "+ stringThreadLocal.get()); }).start(); Thread.sleep(200); System.out.println(" Main thread fetch: "+ stringThreadLocal.get());Copy the code
The output is as follows:
Child thread access: Test main thread access: NULLCopy the code
Doesn’t feel like something’s wrong. ThreadLocal = ArraList; ThreadLocal = ArraList; ThreadLocal = ArraList;
ArrayList<String> arrayList = new ArrayList<>(); New Thread(() -> {arraylist.add (" test "); System.out.println(" child thread fetch: "+ arrayList.get(0)); }).start(); Thread.sleep(200); System.out.println(" arrayList.get(0));Copy the code
The output is as follows:
Child thread acquisition: tests main thread acquisition: testsCopy the code
Take a look at the following code:
ThreadLocal<String> stringThreadLocal = new ThreadLocal<>(); StringThreadLocal. Set (" test "); Thread.sleep(200); New Thread(() -> {system.out.println (" child Thread: "+ threadLocal.get ())); }).start(); System.out.println(" Main thread fetch: "+ stringThreadLocal.get());Copy the code
The following output is displayed:
Main thread acquisition: Test child thread acquisition: NULLCopy the code
ThreadLoal’s set() assignment appears to be restricted to the current thread. How does ThreadLoal achieve this effect? Talk is cheap,show me the fuck code.
The body of the
The main text highlights most of the functions of ThreadLocal itself and ThreadLocalMap that are closely related to ThreadLocal
ThreadLocal itself
Let’s start with the constructor:
public ThreadLocal() {
}
Copy the code
Nothing special, let’s look at the set() function:
The set () function
Public void set(T value) {public void set(T value) { ThreadLocalMap map = getMap(t); if (map ! = null) // Comment 4 map.set(this, value); Else // comment 3 createMap(t, value); }Copy the code
GetMap = ThreadLocalMap; getMap = ThreadLocalMap; getMap = ThreadLocalMap;
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
Copy the code
If threadLocals returns a member variable of Thread, it will initialize and render as follows:
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
Copy the code
ThreadLocals is assigned to ThreadLocal#createMap(), Returning to comment 2 of snippe4, it is clear that the map is null at first, and the logic goes to createMap() of comment 3, where the initialization and assignment of threadLocals is completed. More on ThreadLocalMap later, let’s get() while the iron is hot.
get()
The get() function looks like this:
Public T get() {public T T = thread.currentThread (); ThreadLocalMap map = getMap(t); // comment 2 if (map! Threadlocalmap.entry e = map.getentry (this); threadLocalMap.entry e = map.getentry (this); if (e ! = null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; }} // Comment 4 return setInitialValue(); }Copy the code
As with set(), get the current thread first and then get ThreadLocalMap via getMap(). Since set() was called earlier, map is not null. Get the Entry via ThreadLocalMap#getEntry() and return the value of the Entry if the map is equal to the logic in comment 4:
private T setInitialValue() { T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map ! = null) map.set(this, value); else createMap(t, value); return value; }Copy the code
This can be seen as a set(null) operation, and returns a null value
summary
From the above analysis of set() and get() methods, we can see that both of them need to call getMap() to get threadLocals, and then use threadLocals to value or assign values. ThreadLocals is a member variable of Thread. Different thread instances have their own hreadLocals, which naturally creates thread isolation and answers the question raised at the beginning of this article.
ThreadLocalMap
Since we know from source code that ThreadLoal operations are closely related to ThreadLocalMap, let’s look at the constructor of ThreadLocalMap
The constructor
ThreadLocalMap(ThreadLocal<? > firstKey, Object firstValue) {// Comment 1 Initialize array table = new Entry[INITIAL_CAPACITY]; / / comment by hash and 2 INITIAL_CAPACITY modulus (for a 2 minus one number of the whole power & operation modulus) obtain firstKey position in the array I int I = firstKey. ThreadLocalHashCode & (INITIAL_CAPACITY - 1); Table [I] = new Entry(firstKey, firstValue); size = 1; Note 4 Set the capacity expansion threshold setThreshold(INITIAL_CAPACITY). }Copy the code
The underlying storage structure is an array by modulating the hash value of a ThreadLocal to the default capacity, inserting, updating, deleting, and expanding as necessary
Entry
Take a look at the Entry class
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
Copy the code
WeakReference<ThreadLocal<? >> is a weak reference, that is, when there is no more memory, the trigger GC will be collected
Insert data
As we know from snippet 4, the ThreadLocalMap#set() function calls ThreadLocalMap#set() as its own key, passing it to ThreadLocalMap#set() as follows:
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]; for (Entry e = TAB [I]; e ! = null; E = TAB [I = nextIndex(I, len)]) {ThreadLocal<? > k = e.get(); If (k == key) {if (k == key) {if (k == key) { return; If (k == null) {replaceStaleEntry(key, value, I); return; TAB [I] = new Entry(key, value); // comment 7size + 1 int sz = ++size; // Clear dirty data and determine whether the capacity needs to be expanded. cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }Copy the code
ThreadLocalMap#set() : if the index I in the array is null, add it to ThreadLocalMap#set(). If the index I is null, add it to ThreadLocalMap#set(). Or find a dirty data then directly replace, otherwise find the location closest to I, insert: flow chart is as follows:
Read the data
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 return getEntryAfterMiss(key, i, e); }Copy the code
The logic is simple and doesn’t require much explanation
summary
ThreadLocalMap’s underlying data structure is an array that resolves hash collisions using open addresses.
conclusion
In view of the length of this article, we decided to implement the details of ThreadLocalMap, such as deleting data, scaling, and cleaning up dirty data