This is the second day of my participation in the First Challenge 2022

preface

  • The use of ThreadLocal is one of the more common classes in a real project, primarily addressing the problem of data sharing between threads. It is also a basic knowledge point that must be mastered in back-end development.
  • Data sharing between threads depends on the actual scenario.
    • For example, if the scenario requires alternate printing of something or cumulative counting between threads, the data needs to be shared.
    • For example, the user information passed in the thread should avoid thread sharing;
  • There are a lot of articles about ThreadLocal on the web, and this article will give you some general principles based on your own understanding of it.

The sample

  • ThreadLocal is relatively simple to use, and here is a simple demo.
  • In the demo, three threads are created, each working on the same ThreadLocal object, but the output is the name of each thread, indicating thread isolation.
/** * @Author: ZRH */ @Slf4j public class ThreadLocalDemo { private static ThreadLocal<String> local = ThreadLocal.withInitial(() -> Thread.currentThread().getName()); private static ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 3, 30, TimeUnit.SECONDS, new LinkedBlockingQueue<>(5)); public static void main (String[] args) { for (int i = 0; i <= 2; I++) {executor. Execute (() - > {the info (" thread of this print name: {} ", the local, the get ()); }); }}} -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- print results 10:24:10. 357 / - thread pool - 1-2 INFO Com. Redisson. Web. Demo. ThreadLocalDemo - the threads of the print name: - the thread pool - 1-2 10:24:10. 356 / - thread pool - 1-3 INFO. Com redisson. Web. Demo. ThreadLocalDemo threads - the print name: 357 - thread pool - 1-3 10:24:10. [] - thread pool - 1-1 INFO. Com redisson. Web. The demo. ThreadLocalDemo - the threads of the print name: - thread pool - 1-1Copy the code

The source code parsing

  • ThreadLocal thread isolation is achieved by copying a copy of variable data for each thread, so each thread operates on its own local variable.
  • ThreadLocal can create instances with the normal constructor or withInitial(…) statically. Create an instance.
  • SuppliedThreadLocal is an extended internal class of ThreadLocal that sets specified initial values.
/** * Creates a thread local variable. The initial value of the variable is * determined by invoking the {@code get} method on the {@code Supplier}. * * @param <S> the type of the thread local's value * @param supplier the supplier to be  used to determine the initial value * @return a new thread local variable * @throws NullPointerException if the Specified supplier is null * @since 1.8 */ public static <S> ThreadLocal<S> withInitial(supplier <? extends S> supplier) { return new SuppliedThreadLocal<>(supplier); } /** * Creates a thread local variable. * @see #withInitial(java.util.function.Supplier) */ public ThreadLocal() { }Copy the code
  • There are ThreadLocal member attribute variables in the Thread class with an initial value of NULL.
  • ThreadLocalMap is an internal class of ThreadLocal, which contains an entry[] array. The k passed in its constructor will eventually be referenced by WeakReference, so the key of ThreadLocal instance is a WeakReference and the value is a strong reference.
  • In the ThreadLocalMap constructor, we create an entry[] array of size 16, then compute a hash index using firstKey, add the entry object with the value to the array, and set the next expansion limit.
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 in it. */ 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
  • Threadlocal.set (…) The ThreadLocalMap method adds elements to the ThreadLocalMap.
    • We get the current Thread object Thread, and then we get the ThreadLocalMap collection through the current Thread
    • If ThreadLocalMap is not null, it is added to the ThreadLocalMap collection via the ThreadLocal instance for key and value
    • If ThreadLocalMap is null, a new ThreadLocalMap() object is added to the collection with the ThreadLocal instance as key and value
/** * 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); }... /** * 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
  • The ThreadLocalMap object structure is similar to that of HashMap, but there are differences in its set method. The most important difference is that the hash conflict is resolved by open addressing, while HashMap is resolved by linked list + red-black tree.
    • First compute the array index corresponding to the key,
    • Then check whether the array’s key is the same as the currently inserted key,
    • Replace value if it is equal and return.
    • If not, call replaceStaleEntry(…) The entry () method clears an array of unwanted keys,
    • Then execute nextIndex(…) Method to get the array element at index +1, loop again,
    • If none of the entries is matched, delete some useless entries and determine whether to expand the capacity again.
/** * 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
  • Threadlocal.get (…) The ThreadLocalMap method fetches elements from ThreadLocalMap.
    • Get the current Thread object first, and then get the ThreadLocalMap object,
    • Then pass getEntry(…) Method that takes the current ThreadLocal instance as an argument to get the Entry element,
    • If the Entry was obtained successfully, it is returned directly, otherwise the setInitialValue() method is executed.
. /** * Returns the value in the current thread's copy of this * thread-local variable. If the variable has no value for the * current thread, it is first initialized to the value returned * by an invocation of the {@link #initialValue} method. * * @return the current thread's value of this thread-local */ public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map ! = null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e ! = null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); }Copy the code
  • In ThreadLocalMap getEntry (…). Method:
    • The ThreadLocal instance is passed in as key and an array index is calculated.
    • Retrieves an Entry element based on the subscript, and returns it if the element is the same as the key.
    • Otherwise enter getEntryAfterMiss(…) Method to continue the search:
    • Check whether the incoming entry key is the same this time.
    • If not, execute expungeStaleEntry(…) Clears an entry element in an array whose key is null.
    • Then execute nextIndex(…) The +1 () method returns the element in the array and iterates through the loop again
    • If nothing is found, null is returned.
. /** * Get the entry associated with key. This method * itself handles only the fast path: a direct hit of existing * key. It otherwise relays to getEntryAfterMiss. This is * designed to maximize performance for  direct hits, in part * by making this method readily inlinable. * * @param key the thread local object * @return the entry associated  with key, or null if no such */ 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); } /** * Version of getEntry method for use when key is not found in * its direct hash slot. * * @param key the thread local object * @param i the table index for key's hash code * @param e the entry at table[i] * @return the entry associated with key, or null if no such */ private Entry getEntryAfterMiss(ThreadLocal<? > key, int i, Entry e) { Entry[] tab = table; int len = tab.length; while (e ! = null) { ThreadLocal<? > k = e.get(); if (k == key) return e; if (k == null) expungeStaleEntry(i); else i = nextIndex(i, len); e = tab[i]; } return null; }Copy the code

Cause of memory leak

  • In the above analysis, you can see that the key(ThreadLocal instance) in the Entry object in the ThreadLocalMap is a weak reference, which means that it will not survive more than one GC.
  • So when there is no external strong reference to the current ThreadLocal instance, let it be reclaimed by the JVM system.
  • Why this design, there are a lot of explanations online, readers can understand, here press not table.
  • In a real project, threads are typically created to perform tasks using thread pools, such as the Tomact thread pool. When the weakly referenced key is reclaimed, the corresponding entry should also be reclaimed. However, due to Thread reuse, the Thread->ThreadLocalMap->Entry link still exists. As a result, useless entries cannot be reclaimed all the time, resulting in memory leakage.
  • Threadlocalmap.remove (…) , ThreadLocalMap. GetEntryAfterMiss (…). And ThreadLocalMap. Set (…). Such methods have the operation of clearing useless entries.
  • However, there is no way to avoid the possibility of memory leaks, so here are a few scenarios where ThreadLocal can cause memory leaks:
    • In a real project, if you use thread pools to perform tasks, there will be thread reuse.
    • When ThreadLocal objects are used, there is no manual remove(), no re-get () or set() elements or capacity expansion triggered.

The last

  • ThreadLocal has other drawbacks besides the aforementioned memory leaks. A ThreadLocal variable cannot be passed when a child thread is created in the current thread.
  • Advanced: If you want to implement parent-child thread passing variables, you can use an InheritableThreadLocal object to pass variables.
  • Learn with an open mind and make progress together