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