This is the 13th day of my participation in the November Gwen Challenge. Check out the event details: The last Gwen Challenge 2021.

How ThreadLocal works

All null-key values in ThreadLocalMap are cleared by get() and set() of ThreadLocal. The remove() method of ThreadLocal disconnects the weak reference to the key in the Entry, sets it to NULL, and then clears the value corresponding to the null key.

eachThreadMaintain aThreadLocalMapMapping table, mapping table ofkeyisThreadLocalInstance, and usingThreadLocalIs a weak reference to the value specified to be storedObject. The following diagram shows the reference relationships between these objects, with solid arrows representing strong references and hollow arrows representing weak references.

ThreadLocal local = new ThreadLocal();
local.set("Current thread name:"+Thread.currentThread().getName());// Add ThreadLocal as the key to threadlocals.entry
Thread t = Thread.currentThread();The referent of the threadLocals.Entry array is set to Local. The referent is the key in the Entry that is wrapped by WeakReference
local = null;// When the Entry is set to null, the result in the Entry cannot be affected. // When the Entry is set to null, the Entry will not be affected
System.gc();//执行GC
t = Thread.currentThread();// At this time, the Entry referent is null, which is discarded by GC, because the relationship between Entry and key is WeakReference, and it is reclaimed without other strong references
// If WeakReference is not used here, even if local=null, the key of the Entry will not be recovered, because the Entry and key are strongly associated
If the thread runs for a very long time, even if the referent GC is performed, the value does not clear, there is a risk of memory overflow
// The best way to completely recycle is to call remove
/ / that is: local. Remove (); //remove removes the element from ThreadLocalMap, not itself
System.out.println(local);
Copy the code

Use InheritableThreadLocal, ThreadLocal ThreadLocal = new InheritableThreadLocal (), so in the child thread can through the get method to get to the main thread set method set value.

ThreadLocalMap structure

static class ThreadLocalMap {
	/** * the storage structure of the key-value pair entity */
	static class Entry extends WeakReference<ThreadLocal<? >>{
		// The value associated with the current thread is not traced by weak references
		Object value;

		/** * constructs the key-value pair **@paramK k is the key, and ThreadLocal as the key is wrapped as a weak reference *@paramV v for value */Entry(ThreadLocal<? > k, Object v) {super(k); value = v; }}// The initial capacity must be a power of 2
	private static final int INITIAL_CAPACITY = 16;

	// Store an array of ThreadLocal key-value entities, which must be a power of 2
	private Entry[] table;

	// Number of ThreadLocalMap elements
	private int size = 0;

	// The threshold for expansion. The default is 2/3 of the array size
	private int threshold;
}
Copy the code

Unlike Java, linear detection is used.

private int expungeStaleEntry(int staleSlot) {
    Entry[] tab = table;
    int len = tab.length;

    // expunge entry at staleSlot
    tab[staleSlot].value = null;
    tab[staleSlot] = null;
    size--;     // Set the value of entry to NULL so that GC can free up the memory occupied by the real value. Assign entry to null and reduce size by 1 so that the slot can be replaced with new entries

    // Rehash until we encounter null
    Entry e;
    int i;
    for (i = nextIndex(staleSlot, len); // The index after staleSlot is iterated backwards until a null entry is encountered(e = tab[i]) ! =null; i = nextIndex(i, len)) { ThreadLocal<? > k = e.get();if (k == null) {    // If the entry key is null, the entry is cleared
            e.value = null;
            tab[i] = null;
            size--;
        } else {
            int h = k.threadLocalHashCode & (len - 1);
            if(h ! = i) {// The hash value of the key is not equal to the current index, indicating that the entry is moved back to the current index due to hash conflicts
                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)      // Hash the entry again and resolve the conflicth = nextIndex(h, len); tab[h] = e; }}}return i;   // Returns the index value of the first null entry after staleSlot
}
Copy the code

In the process of traversing backwards from the ith entry, the corresponding key entry is directly returned when it is found. If an entry with a null key is encountered, the expungeStaleEntry method is called to clear it.

ThreadLocalMap set method

private void set(ThreadLocal<? > key, Object value) { // We don't use a fast path as with get() because it is at // least as common to use set() to create new  entries as // it is to replace existing ones, in which case, a fast // path would fail more often than not. 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) { replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); int sz = ++size; if (! cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }Copy the code

ThreadLocalMap remove method

After finding the exact entry corresponding to the key, call the entry clear method, and then call expungeStaleEntry to clear the entry whose key is null.

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 (LLDB () == key) {e = TAB [I = nextIndex(I, len)]) {if (LLDB () = key) { // Call Entry's clear method, as shown in code 2 expungeStaleEntry(I); // This clears an entry with a null key. }}}Copy the code

How to use it

Look at the test code

package thread;

public class ThreadLocalDemo {
    /** * ThreadLocal variable, each thread has a copy of each other */
    public static final ThreadLocal<String> THREAD_LOCAL = new ThreadLocal<>();

    public static void main(String[] args) throws Exception {
        new ThreadLocalDemo().threadLocalTest();
    }

    public void threadLocalTest(a) throws Exception {
        // Set the main thread value
        THREAD_LOCAL.set("mainthreadvalue");// Set the key to the main thread
        String v = THREAD_LOCAL.get();
        System.out.println("Before thread-0 Thread executes," + Thread.currentThread().getName() + "Thread fetched value:" + v);

        new Thread(new Runnable() {
            @Override
            public void run(a) {
                String v = THREAD_LOCAL.get(); // The key is null for the current thread channel
                System.out.println(Thread.currentThread().getName() + "Thread fetched value:" + v);
                / / set the threadLocal
                THREAD_LOCAL.set("inThreadValue"); // Set the key to the current key
                v = THREAD_LOCAL.get();
                System.out.println("After resetting," + Thread.currentThread().getName() + "The thread fetched the value:" + v);
                System.out.println(Thread.currentThread().getName() + "Thread execution terminated");
            }
        }).start();
        // Wait for all threads to finish executing
        Thread.sleep(3000L);
        v = THREAD_LOCAL.get();
        System.out.println("After thread-0 Thread is executed," + Thread.currentThread().getName() + "Thread fetched value:"+ v); }}Copy the code

Execution Result:

Mainthread0: mainThread0: null: thread0: null InThreadValue Thread-0 End Thread-0 after the main Thread executes: mainThreadValueCopy the code

The interview guide

Interviewer: So how does the memory leak from weak references in ThreadLocal happen?

Small white: If a ThreadLocal has no external strong references, the ThreadLocal must be collected when garbage collection occurs (weak references are collected regardless of whether the current memory is sufficient or not), resulting in null entries in the ThreadLocalMap. The external will not be able to obtain the value of these entries with a null key, and if the current thread is always alive, there will be a strong reference chain: Thread Ref -> Thread -> ThreaLocalMap -> Entry -> Value. As a result, the Object corresponding to the value cannot be reclaimed, resulting in memory leakage.

Interviewer: How do you solve it?

Small white: The get, set, and remove methods of ThreadLocal remove all values with a null key. However, a memory leak can occur when a ThreadLocal get or set method is used. A null value will not be cleared if the get, set, or remove methods are not called thereafter. The solution is to call its remove() method every time a ThreadLocal is used to remove data, or as the JDK recommends, make the ThreadLocal variable private static so that a strong reference to ThreadLocal always exists. This ensures that the Entry value can be accessed through ThreadLocal’s weak reference at any time, and then erased.

Welcome to pay attention to the public number: programmer wealth free road