One, what is it? How does it work?

What is?

Is a local variable for each thread and can store variables unique to each thread.

How does it work?

You can create a variable object unique to each thread

Data isolation between threads can be achieved

Spring uses ThreadLocal for database isolation in declarative transactions


Second, class architecture

ThreadLocal properties

/** * This value is used for the bucket position of linear probe insertion */ when storing values in ThreadLocalHashMap
private final int threadLocalHashCode = nextHashCode();

/** * The next hashCode to be given, each atomic update, starts at 0 */
private static AtomicInteger nextHashCode = new AtomicInteger();

/** * hashCode is a value that uses this number to distribute the key evenly across an array of powers of 2 https://www.javaspecialists.eu/archive/Issue164-Why-0x61c88647.html * because it's complicated, don't discuss here * /
private static final int HASH_INCREMENT = 0x61c88647;
Copy the code

ThreadLocalMap properties

/** * Map initial capacity */
private static final int INITIAL_CAPACITY = 16;

/** * hash table, the length is always a power of 2, the reason is that the power of 2 -1 binary is all 1 * easy to press and operation * such as 16: 10000 -1 = 1111 * press and after all in the array */
private Entry[] table;

/** * The length of the hash table */
private int size = 0;

/** * Capacity expansion threshold, default is 0, capacity expansion is 2/3 of the length of the hash table */
private int threshold;
Copy the code

Three, the implementation principle

1. Why can ThreadLocal achieve thread isolation?

/** * create a ThreadLocal and see that nothing is being done inside the constructor */
public ThreadLocal(a) {}
/** * This method is called to set values in ThreadLocal
public void set(T value) {
    // Get the current thread
    Thread t = Thread.currentThread();
    // Get the variable threadLocals from the current thread
    ThreadLocalMap map = getMap(t);
    if(map ! =null)
        map.set(this, value);
    else
        // If the current thread threadLocals variable is empty, create map Settings
        createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
    // Get the thread variable of t
    return t.threadLocals;
}
Copy the code

If you want to set a value for a ThreadLocal, you will need to set the value for the local threadLocals variable


2. Add, delete and check operations

/** * This method is called to set values in ThreadLocal
public void set(T value) {
    // Get the current thread
    Thread t = Thread.currentThread();
    // Get the variable threadLocals from the current thread
    ThreadLocalMap map = getMap(t);
    if(map ! =null)
        map.set(this, value);
    else
        // If the current thread threadLocals variable is empty, create map Settings
        createMap(t, value);
}
void createMap(Thread t, T firstValue) {
    // Set the variable value for the thread
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}
/** * sets the value associated with the key, which is ThreadLocal */
private void set(ThreadLocal
        key, Object value) {
    Entry[] tab = table;
    int len = tab.length;
    // Get the insertion position by bitwise
    int i = key.threadLocalHashCode & (len - 1);
    /** * Uses linear probe to insert the value * traverses backwards from the obtained subscript, if the current key is equal to the current key in the array */ Replaces the current subscript entry */ if the current subscript position in the array is empty
    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) {
            // The current position is empty and needs to be replaced
            replaceStaleEntry(key, value, i);
            return; }}// If I does not have the same key backward or an empty position, it replaces the entry of the current position
    tab[i] = new Entry(key, value);
    int sz = ++size;
    Delete some empty entries from the hash table. If the size is larger than 3/4 of the threshold, you need to expand the hash table
    if(! cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }/** * replaces invalid entry */
private void replaceStaleEntry(ThreadLocal<? > key, Object value,int staleSlot) {
    Entry[] tab = table;
    int len = tab.length;
    Entry e;

    int slotToExpunge = staleSlot;
    for (inti = prevIndex(staleSlot, len); (e = tab[i]) ! =null; i = prevIndex(i, len))
        // Look for space from back to front, and find the furthest position from staleSlot
        if (e.get() == null)
            slotToExpunge = i;

    // iterate backwards
    for (inti = nextIndex(staleSlot, len); (e = tab[i]) ! =null; i = nextIndex(i, len)) { ThreadLocal<? > k = e.get();// Find the same key as ThreadLocal
        if (k == key) {
            e.value = value;
            // Replace the entry for the current position
            tab[i] = tab[staleSlot];
            / / exchange of entry
            tab[staleSlot] = e;

            // If there is no space in front, set the delete position
            if (slotToExpunge == staleSlot)
                slotToExpunge = i;
            // Delete the following empty position
            cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
            return;
        }
        if (k == null && slotToExpunge == staleSlot)
            slotToExpunge = i;
    }
    // If no key is found, replace it directly
    tab[staleSlot].value = null;
    tab[staleSlot] = new Entry(key, value);
    // An empty entry needs to be deleted before staleSlot
    if(slotToExpunge ! = staleSlot) cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); }/** * rehash hash table */
private void rehash(a) {
    // Delete invalid entries and rehash the hash table
    expungeStaleEntries();
    // If the number is greater than or equal to 3/4 of the threshold, you need to expand the capacity
    if (size >= threshold - threshold / 4)
        resize();
}
/** * delete invalid entry and rehash hash table */
private void expungeStaleEntries(a) {
    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); }}/** * Double the hash table */
private void resize(a) {
    Entry[] oldTab = table;
    int oldLen = oldTab.length;
    int newLen = oldLen * 2;
    Entry[] newTab = new Entry[newLen];
    int count = 0;
    for (int j = 0; j < oldLen; ++j) {
        Entry e = oldTab[j];
        if(e ! =null) {
            // If the key of the current position is empty, GC is performed directlyThreadLocal<? > k = e.get();if (k == null) {
                e.value = null; // Help the GC
            } else {
                // Get the new location
                int h = k.threadLocalHashCode & (newLen - 1);
                // Find an empty position from the current position
                while(newTab[h] ! =null)
                    h = nextIndex(h, newLen);
                // Insert entry againnewTab[h] = e; count++; }}}// Reset the threshold and hash table properties
    setThreshold(newLen);
    size = count;
    table = newTab;
}
/** * Delete some entries. Delete */ in half
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];
        if(e ! =null && e.get() == null) {
            n = len;
            removed = true;
            i = expungeStaleEntry(i);
        }
        // Unconditionally move 1 bit to the right /2
    } while ((n >>>= 1) != 0);
    return removed;
}
/** * Delete the entry of the specific position and rehash the entry */
private int expungeStaleEntry(int staleSlot) {
    Entry[] tab = table;
    int len = tab.length;
    // Delete the entry of the current subscript
    tab[staleSlot].value = null;
    tab[staleSlot] = null;
    size--;
    Entry e;
    int i;
    // Iterate backwards from the current position to find an entry that is empty or after rehash
    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);
            // The position after rehash is not the current position and the current entry needs to be deleted
            if(h ! = i) {//help GC
                tab[i] = null;
                // Walk backwards from h to find an empty position
                while(tab[h] ! =null)
                    h = nextIndex(h, len);
                / / insert the entrytab[h] = e; }}}return i;
}
Copy the code
/** * get the value */ stored in ThreadLocal
public T get(a) {
    //1, get the current thread
    Thread t = Thread.currentThread();
    //2 to get the threadLocals variable from the current thread, which is the ThreadLocalMap object
    ThreadLocalMap map = getMap(t);
    if(map ! =null) {
        //3, get the value set earlier and return
        ThreadLocalMap.Entry e = map.getEntry(this);
        if(e ! =null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            returnresult; }}If the threadLocals variable of the current thread is empty, it has not been initialized and needs to be initialized
    return setInitialValue();
}
/** * sets the initialization value */
private T setInitialValue(a) {
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if(map ! =null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}
/** * The initialization value is null */
protected T initialValue(a) {
    return null;
}
Copy the code
/** * delete variable */
public void remove(a) {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if(m ! =null)
        m.remove(this);
}
/** * Delete an entry */
private void remove(ThreadLocal
        key) {
    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len - 1);
    // iterate backwards from I
    for(Entry e = tab[i]; e ! =null; e = tab[i = nextIndex(i, len)]) {
        if (e.get() == key) {
            //help GC
            e.clear();
            // Delete the current entry and rehash the entry following it
            expungeStaleEntry(i);
            return; }}}Copy the code

Third, there are problems

1. Memory leakage

Since using ThreadLocal is essentially using a ThreadLocalMap, you cannot manually remove the key(reference to the ThreadLocalMap) from the ThreadLocalMap after using the ThreadLocal, which can cause memory leaks With this in mind, we set the key in the ThreadLocalMap to a WeakReference, which is easily removed by GC, but we still need to manually remove the Th by calling ThreadLocal’s remove method when we’re done using it ReadLocal references to avoid memory leaks.

2. The child thread cannot access the parent thread variable

You can use InheritableThreadLocal

The principle of

ThreadLocal<String> local = new InheritableThreadLocal<>();
local.set("main local variable");
public void set(T value) {
    // Get the current thread
    Thread t = Thread.currentThread();
    [InheritableThreadLocal] [InheritableThreadLocal] [InheritableThreadLocal] [InheritableThreadLocal] [InheritableThreadLocal] [InheritableThreadLocal] [InheritableThreadLocal] [InheritableThreadLocal Cals variable
    ThreadLocalMap map = getMap(t);
    if(map ! =null)
        map.set(this, value);
    else
        // If the current thread threadLocals variable is empty, create map Settings
        createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
    return t.inheritableThreadLocals;
}
// Create a child thread
Thread thread = new Thread(() -> {
    local.set("child thread variable");
    System.out.println("child thread get local variable : " + local.get());
});
public Thread(Runnable target) {
    init(null, target, "Thread-" + nextThreadNum(), 0);
}
private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
    init(g, target, name, stackSize, null.true);
}
private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
    // Get the current thread. This is the parent thread. The child thread has not been created yetThread parent = currentThread(); ./ / the parent here. InheritableThreadLocals when parent set has been initialized
        if(inheritThreadLocals && parent.inheritableThreadLocals ! =null)
            // Subclasses also use inheritableThreadLocals instead of threadLocals
            this.inheritableThreadLocals =
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
}
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
    return new ThreadLocalMap(parentMap);
}
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) {
            // Upward transition
            @SuppressWarnings("unchecked")
            ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
            if(key ! =null) {
                // Call the childValue method on the InheritableThreadLocal object, returning e.value
                Object value = key.childValue(e.value);
                Entry c = new Entry(key, value);
                // Insert the inheritableThreadLocals data from the parent thread into the child thread
                int h = key.threadLocalHashCode & (len - 1);
                while(table[h] ! =null) h = nextIndex(h, len); table[h] = c; size++; }}}}Copy the code