Q: What do you understand about ThreadLocal

Analysis: In multithreaded environments, it is common to encounter a scenario where global variables in a class are maintained. If multiple threads operate on the same global variable at the same time, there will be resource contention problems, resulting in incorrect data results. How do you guarantee the correctness of a variable’s value (the atomicity of its modification)?

  • Modify the code lock to ensure that only one thread modifies the variable value at a time

  • And contract awarding AtomicXXX

  • ThreadLocal

    Each thread has a Map(ThreadLocalMap) that stores the defined ThreadLocal object as Key, the name of my custom value value value. The static inner class of ThreadLocalMap (described below) is derived from the parent Thread we wrote that inherits from the live application. This mechanism ensures the isolation of the variable value between multiple threads.

    What exactly is a ThreadLocal?

Review the Java memory model:

Second, the role of

Function 1: Data interaction between threads is completed through frequent reads and writes between the working memory and main memory. However, data is stored in the thread local memory, which improves the access efficiency and avoids the decrease of CPU throughput caused by thread blocking. Function two: in multithreading, each thread needs to maintain a local variable, easy to complete the operation of thread exclusive resources.

Why does ThreadLocal cause memory leaks

Synchronized is time for space, and ThreadLocal is space for time.

Because synchronized manipulates data, it only needs to store a single variable in main memory and blocks shared variables, whereas ThreadLocal creates a small heap of working memory for each thread. Obviously, this proves the above statement.

Each thread corresponds to a block of working memory, and threads can store multiple ThreadLocal’s. Therefore, if 10,000 threads are started and each thread creates 10,000 ThreadLocal’s, each thread maintains 10,000 ThreadLocal’s small memory space. Moreover, when the thread finishes executing, assuming that the entries in these ThreadLocal’s will not be reclaimed, it will easily cause the heap memory to overflow.

How to do? Doesn’t the JVM provide a solution?

ThreadLocal does, of course, so they set the Entry in the ThreadLocal to weak references, and when garbage is collected, the ThreadLocal is reclaimed.

What is a weak reference?

  1. The reference to the Key of a ThreadLocal map is a strong reference and is not reclaimed. If you do not reclaim manually, ThreadLocal will not reclaim and this will cause a memory leak.
  2. Key with weak references: The object referenced by ThreadLocal is reclaimed, and the Key referenced by ThreadLocalMap is weakly referenced. If memory is reclaimed, then the Key that mapped the ThreadLocalMap is reclaimed, and the ThreadLocal is reclaimed. The value is cleared when ThreadLocalMap calls get, set, and remove.
  3. Comparing the two cases, we can find that: due toThreadLocalMapThe life cycle followsThreadThe same length, if no manual delete correspondingkey, can cause memory leaks, but using weak references provides an additional layer of protection:A weak referenceThreadLocalNo memory leaks, correspondingvalueThe next timeThreadLocalMapcallset.get.removeWill be cleared.

So if the JVM is guaranteed, what kind of memory leaks are there?

ThreadLocalMap uses ThreadLocal objects as weak references. When garbage is collected, the Key in the ThreadLocalMap will be reclaimed, that is, the Entry with the Key set to NULL. If the thread doesn’t terminate too long, that is, the ThreadLocal object will never be recycled, which will also result in a memory leak if there are too many threads +TheradLocal.

In ThreadLocal, when the remove, get, or set methods are called, null weak references are removed. Conclusion:

  1. The JVM avoids memory leaks by setting ThreadLocalMap’s Key to a weak reference.
  2. The JVM retrieves weak references when the remove, get, and set methods are called.
  3. When ThreadLocal stores many entries with null keys instead of calling remove, get, and set methods, memory leaks can occur.
  4. When static ThreadLocal is used, extending the lifetime of ThreadLocal can also lead to memory leaks. Because static variables are loaded when the class is not loaded, they are not necessarily recycled when the thread terminates. Then, static’s extended lifetime is more likely to cause a memory leak than normal member variables that are loaded when they are used.

ThreadLocal source code summary

  1. Thread local variables should be stored in Thread objects. Thread local variables should be stored in Thread objects.
public class Thread implements Runnable { /* Make sure registerNatives is the first thing <clinit> does. */ private static native void registerNatives(); static { registerNatives(); } / /... . /* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null; /* * InheritableThreadLocal values pertaining to this thread. This map is * maintained by the InheritableThreadLocal class. */ ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;Copy the code

We can see the Thread local variable is stored in a Thread objects threadLocals attributes, and threadLocals attribute is a ThreadLocal ThreadLocalMap object.

  1. Then we see the ThreadLocal. Where ThreadLocalMap is sacred
/**
     * ThreadLocalMap is a customized hash map suitable only for
     * maintaining thread local values. No operations are exported
     * outside of the ThreadLocal class. The class is package private to
     * allow declaration of fields in class Thread.  To help deal with
     * very large and long-lived usages, the hash table entries use
     * WeakReferences for keys. However, since reference queues are not
     * used, stale entries are guaranteed to be removed only when
     * the table starts running out of space.
     */
    static class ThreadLocalMap {
        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
        /**
         * The initial capacity -- MUST be a power of two.
         */
        private static final int INITIAL_CAPACITY = 16;
        /**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         */
        private Entry[] table;
        // ... ...
        /**
         * Construct a new map initially containing (firstKey, firstValue).
         * ThreadLocalMaps are constructed lazily, so we only create
         * one when we have at least one entry to put init. */ ThreadLocalMap(ThreadLocal<? > firstKey, Object firstValue) { table = new Entry[INITIAL_CAPACITY]; int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); table[i] = new Entry(firstKey, firstValue); size = 1;setThreshold(INITIAL_CAPACITY);
        }
Copy the code

Can see ThreadLocal. ThreadLocalMap ThreadLocal is a static inner class. A ThreadLocalMap is literally a map that holds a ThreadLocal object (with its Key), yes, but a ThreadLocal object wrapped in two layers. The first layer of wrapping is to use WeakReference<ThreadLocal> to turn a ThreadLocal object into a WeakReference object. The second layer of packaging is to define a special class Entry to extend WeakReference> :

static class Entry extends WeakReference<ThreadLocal<? >> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<? > k, Object v) { super(k); value = v; }}Copy the code

The Entry class is obviously an entity that holds map key-value pairs, with ThreadLocal as the key and the thread-local variable as value. Constructor of WeakReference called by super(k), which means to convert ThreadLocal object into WeakReference object and use it as key.

From ThreadLocalMap’s constructor:

ThreadLocalMap(ThreadLocal<? > firstKey, Object firstValue) { table = new Entry[INITIAL_CAPACITY]; int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); table[i] = new Entry(firstKey, firstValue); size = 1;setThreshold(INITIAL_CAPACITY);
        }
Copy the code

As you can see, the implementation of ThreadLocalMap uses an array private Entry[] table to hold key-value pairs. The initial size is 16. ThreadLocalMap implements the mapping from key to value: firstKey.threadLocalHashCode & (INITIAL_CAPACITY – 1)

/**
     * ThreadLocals rely on per-thread linear-probe hashmaps attached * to each thread (Thread.threadLocals and * inheritableThreadLocals). The ThreadLocal objects act as keys,  * searched via threadLocalHashCode. This is a customhash code
     * (useful only within ThreadLocalMaps) that eliminates collisions
     * in the common case where consecutively constructed ThreadLocals
     * are used by the same threads, while remaining well-behaved in
     * less common cases.
     */
    private final int threadLocalHashCode = nextHashCode();
    /**
     * The next hash code to be given out. Updated atomically. Starts at
     * zero.
     */
    private static AtomicInteger nextHashCode = new AtomicInteger();
    /**
     * The difference between successively generated hash codes - turns
     * implicit sequential thread-local IDs into near-optimally spread
     * multiplicative hash values for power-of-two-sized tables.
     */
    private static final int HASH_INCREMENT = 0x61c88647;
    /**
     * Returns the next hash code.
     */
    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }
Copy the code

Using a static atomic attribute AtomicInteger nextHashCode, increment HASH_INCREMENT = 0x61C88647 each time Then & (INITIAL_CAPACITY – 1) gets the index in the array private Entry[] table.

  1. Let’s have a look at the Thread objects of ThreadLocal. ThreadLocalMap threadLocals = null; How to initialize:
/**
     * Sets the current thread's copy of this thread-local variable * to the specified value. Most subclasses will have no need to * override this method, relying solely on the {@link #initialValue} * method to set the values of thread-locals. * * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if(map ! = null) map.set(this, value);else
            createMap(t, value);
    }
    /**
     * Get the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param  t the current thread
     * @return the map
     */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    /**
     * Create the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the map
     */
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
Copy the code

If t. htreadlocals is null when ThreadLocal calls set, the ThreadLocalMap of the Thread has not been initialized. So call createMap to initialize: t.htreadlocals = new ThreadLocalMap(this, firstValue);

Note the lazy initialization technique used here:

/**
         * Construct a new map initially containing (firstKey, firstValue).
         * ThreadLocalMaps are constructed lazily, so we only create
         * one when we have at least one entry to put init. */ ThreadLocalMap(ThreadLocal<? > firstKey, Object firstValue) { table = new Entry[INITIAL_CAPACITY]; int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); table[i] = new Entry(firstKey, firstValue); size = 1;setThreshold(INITIAL_CAPACITY);
        }
Copy the code

Only the 16-element reference array is initialized, not the 16-entry object. It’s how many thread-local objects there are in a thread to hold, and how many entries are initialized to hold them.

At this point, we can think about why we did it this way. Why use ThreadLocalMap to hold thread-local objects? The reason is that a thread can have many local objects, so that no matter how many local variables you have in a thread, you use the same ThreadLocalMap to store them. The initial size of the private Entry[] table in ThreadLocalMap is 16. If the capacity exceeds two-thirds, the system expands the capacity.

  1. Let’s take a look at the threadlocal.set method:
/**
    * Sets the current thread's copy of this thread-local variable * to the specified value. Most subclasses will have no need to * override this method, relying solely on the {@link #initialValue} * method to set the values of thread-locals. * * @param value the value to be stored in the current thread's copy of
    *        this thread-local.
    */
   public void set(T value) {
       Thread t = Thread.currentThread();
       ThreadLocalMap map = getMap(t);
       if(map ! = null) map.set(this, value);else
           createMap(t, value);
   }
Copy the code

Set (this, value); set(this, value); Save to private Entry[] table:

/**
         * Set the value associated with key.
         * @param key the thread local object
         * @param value the value to be set
         */
        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
  1. ThreadLocal involves two levels of automatic memory reclamation

1) Memory reclamation at the ThreadLocal level

/* * Each thread holds an implicit reference to its copy of a thread-local * variable as long as the thread is alive and  the {@code ThreadLocal} * instance is accessible; after a thread goes away, all of its copies of * thread-local instances are subject to garbage collection (unless other * references to these copies exist).Copy the code

When a Thread dies, so all the stored in the Thread local variable can be recycled, actually in here refers to the Thread Thread objects ThreadLocal. ThreadLocalMap threadLocals is recycled, it is clearly.

2) Memory reclamation at ThreadLocalMap level:

/**
 * ThreadLocalMap is a customized hash map suitable only for
 * maintaining thread local values. No operations are exported
 * outside of the ThreadLocal class. The class is package private to
 * allow declaration of fields in class Thread.  To help deal with
 * very large and long-lived usages, the hash table entries use
 * WeakReferences for keys. However, since reference queues are not
 * used, stale entries are guaranteed to be removed only when
 * the table starts running out of space.
 */
Copy the code

If a thread lives for a long time and holds a large number of thread-local variables (i.e., a large number of entries), then it is important to reclaim ThreadLocalMap memory during the lifetime of the thread. Otherwise, the larger the number of entries, As ThreadLocalMap gets bigger and bigger, it takes up more memory, so you should clean up Entry objects for thread-local variables that are no longer needed. The way to use it is that the key of the Entry object is the wrapper of WeakReference. When ThreadLocalMap’s private Entry[] table, The threadLocalmap. set method will attempt to reclaim the Entry object if the thread has more than 10 local variables when it is two-thirds occupied:

if(! cleanSomeSlots(i, sz) && sz >= threshold)rehash(a);Copy the code

CleanSomeSlots is to reclaim memory:

/**
         * Heuristically scan some cells looking for stale entries.
         * This is invoked when either a new element is added, or
         * another stale one has been expunged. It performs a
         * logarithmic number of scans, as a balance between no
         * scanning (fast but retains garbage) and a number of scans
         * proportional to number of elements, that would find all
         * garbage but would cause some insertions to take O(n) time.
         *
         * @param i a position known NOT to hold a stale entry. The
         * scan starts at the element after i.
         *
         * @param n scan control: {@code log2(n)} cells are scanned,
         * unless a stale entry is found, in which case
         * {@code log2(table.length)-1} additional cells are scanned.
         * When called from insertions, this parameter is the number
         * of elements, but when from replaceStaleEntry, it is the
         * table length. (Note: all this could be changed to be either
         * more or less aggressive by weighting n instead of just
         * using straight log n. But this version is simple, fast, and
         * seems to work well.)
         *
         * @return true if any stale entries have been removed.
         */
        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); }}while( (n >>>= 1) ! = 0);return removed;
        }
Copy the code

LLDB () == null WeakReference<ThreadLocal<? >> method:

/**
     * Returns this reference object's referent. If this reference object has * been cleared, either by the program or by the garbage collector, then * this method returns null. * * @return The object to which this reference refers, or * null if this reference object has been cleared */ public T get() { return this.referent; }Copy the code

Return null indicating that the Entry key has been reclaimed, so the Entry object can be reclaimed: expungeStaleEntry(I)

/**
         * Expunge a stale entry by rehashing any possibly colliding entries
         * lying between staleSlot and the next null slot.  This also expunges
         * any other stale entries encountered before the trailing null.  See
         * Knuth, Section 6.4
         *
         * @param staleSlot index of slot known to have null key
         * @return the index of the next null slot after staleSlot
         * (all between staleSlot and this slot will have been checked
         * for expunging).
         */
        private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;

            // expunge entry at staleSlot
            tab[staleSlot].value = null;
            tab[staleSlot] = null;
            size--;
Copy the code
  1. ThreadLocal interface:

1) you can override the protected T initialValue() method when you need to specify an initialValue;

2) public T get();

3) public void set(T value);

4) public void remove();

  1. conclusion

1) All local variables in a thread are stored in the same map property of the thread itself;

2) When a thread dies, thread-local variables will automatically reclaim memory;

3) Thread-local variables are saved in map through an Entry, the key of which is a ThreadLocal wrapped by WeakReference and the value is thread-local variable;

The key to the value of the mapping is through: ThreadLocal. ThreadLocalHashCode & accomplished (INITIAL_CAPACITY - 1);Copy the code

4) The collection of entries in ThreadLocalMap will be involved when the number of local variables owned by a thread exceeds 2/3 of its capacity (10 if the capacity is not expanded).