ThreadLocal

Function and Purpose

In multithreading, access to critical sections is usually secured by locking.

However, both optimistic and pessimistic locks can affect performance when concurrency conflicts occur.

ThreadLocal is a “space for time” approach that creates a local variable for each thread so that resources are naturally not contested

use

Public static void main(String[] args) throws InterruptedException {// Lambda expression Supplier initializes ThreadLocal<String> stringThreadLocal = ThreadLocal.withInitial(() -> "init"); ThreadLocal<SimpleDateFormat> formatThreadLocal = new ThreadLocal<>(){@override protected SimpleDateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); }}; Thread t1 = new Thread(()->{ stringThreadLocal.set("1"); stringThreadLocal.get(); Date now = new Date(); System.out.println(formatThreadLocal.get().format(now)); }); Thread t2 = new Thread(()->{ stringThreadLocal.set("2"); }); T1.setname (" child thread 1"); t1.start(); T2.setname (" child thread 2"); t2.start(); Thread.sleep(1000); }Copy the code

The principle of

Each thread has one ThreadLocalMap object, and a thread can have multiple ThreadLocal objects. These ThreadLocal objects and their values are stored in the ThreadLocalMap.Entry array

Initialize the

You can set the initial value for Entry. Value of each thread by setting the initialization mode

<String> stringThreadLocal = threadlocal. withInitial(() -> "init"); ThreadLocal<SimpleDateFormat> formatThreadLocal = new ThreadLocal<>(){@override protected SimpleDateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); }};Copy the code

The initial value is set not when the object is created, but when the get method is called and no value is obtained

Get the value

ThreadLocal = thread.currentThread (); ThreadLocal = thread.currentThread (); ThreadLocal = thread.currentThread (); ThreadLocalMap map = getMap(t); ThreadLocalMap = 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

Set the value

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

Finally, the value is stored in entry.value and the Entry is used as an element of the Entry array

When searching for a subscript position in the array, hash is used to obtain the subscript position. If the subscript is occupied, the next position is determined until the unoccupied position is found

Memory leak problem

While the key in ThreadLocalMap is a weak reference and is automatically reclaimed when no external strong reference exists, the value in Entry is still a strong reference

The reference chain for this value is as follows:

Since the Entry array is a Thread variable that exists throughout the lifetime of the Thread, the value has a chance to be reclaimed only when the Thread ends and is reclaimed.

When ThreadLocalMap detects that a weak-referenced ThreadLocal has been reclaimed, it calls expungeStaleEntry() to release the value

This method not only releases the value of the ThreadLocal, but continues to traverse the Entry array until the Entry== NULL is retrieved

If the weak reference key of the obtained Entry is NULL, the strong reference value is released and the Entry[I] is set to NULL

private int expungeStaleEntry(int staleSlot) { Entry[] tab = table; int len = tab.length; // expunge entry at staleSlot tab[staleSlot].value = null; tab[staleSlot] = null; size--; // Rehash until we encounter null Entry e; int i; 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); if (h ! = i) { 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) h = nextIndex(h, len); tab[h] = e; } } } return i; }Copy the code

ThreadLocal’s set(), get(), and remove() all call this method indirectly

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) // Reclaim key expungeStaleEntry(I); else i = nextIndex(i, len); e = tab[i]; } return null; }Copy the code

As you can see, ThreadLocal is also careful to avoid memory leaks. Not only is the key maintained using weak references, but the key is checked for recycling on each operation, and then the value is reclaimed.

However, ThreadLocal’s approach does not guarantee against memory leaks

In extreme cases, the get() method always accesses a fixed number of existing ThreadLocal’s, the cleanup action never takes place, and there is no chance to call set() and remove(), and the memory leak will still occur.

When you need the ThreadLocal variable, it’s good for the system to call remove()

InheritableThreadLocal

ThreadLocal that can be inherited

The child thread does not get the parent thread’s ThreadLocal object

If we want the child thread to see the parent thread’s ThreadLocal, we can use InheritableThreadLocal. As the name suggests, this is a ThreadLocal that supports parent-child inheritance between threads

However, the following points should be noted:

  1. The passing of variables occurs when a thread is created. If a thread is used instead of a new thread, it will not work
  2. The assignment of a variable is copied from the map of the main thread to the child thread, and their value is the same object. If the object itself is not thread-safe, then there is a thread-safe problem