🌹 welcome wechat attention [Java programming], progress a little bit every day, precipitation technology to share knowledge.

Gossip ThreadLocal

Previously, I used ThreadLocal in GitHub repository V-LoggingTool. It is mainly used in the section class. The function needs to retrieve the user information intercepted by the pre-enhancement temporarily, and then use the user information from this ThreadLocal when executing the post-enhancement.

Today we are talking about the relevant knowledge of ThreadLocal, to understand his data structure, usage, principle, etc.. Let’s go deeper…

ThreadLocal: ThreadLocalMap: ThreadLocalMap: ThreadLocalMap: ThreadLocalMap: ThreadLocalMap: ThreadLocalMap: ThreadLocalMap: ThreadLocalMap: ThreadLocal It really took me a long time to figure out what was going on.

ThreadLocal tools

ThreadLocal is a local thread replica variable utility class that maps a private thread to its replica object. Variables in each thread do not interfere with each other.

ThreadLocal is a tool class that allows each thread to manipulate variables, so it can isolate variables between threads

Internal structure drawing

The next is to look at the picture and speak:

  • Each Thread Thread has an internal ThreadLocalMap.
  • Map stores the ThreadLocal (key) object and a copy of the thread’s variables (value).
  • The Map inside a Thread is maintained by a ThreadLocal, which is responsible for fetching and setting Thread variable values from the Map.
  • A Thread can have multiple ThreadLocal’s.

Each thread has its own Map structure, in which ThreadLocal is the Key variable copy of Vaule’s key-value pair, so as to achieve the purpose of variable isolation.

How do you normally use ThreadLocal?

package threadlocal;

/ * * *@Auther: Xianglei
 * @Company: The Tao of Java Programming@Date: 2020/7/2 and *@Version1.0 * /
public class main {
    private static ThreadLocal<String> sThreadLocal = new ThreadLocal<>();
    public static void main(String args[]) {
        sThreadLocal.set("This is in the main thread.");
        System.out.println("Thread name:" + Thread.currentThread().getName() + "-" + sThreadLocal.get());
        / / thread a
        new Thread(new Runnable() {
            @Override
            public void run(a) {
                sThreadLocal.set("This is in thread A");
                System.out.println("Thread name:" + Thread.currentThread().getName() + "-"+ sThreadLocal.get()); }},"Thread a").start();
        / / thread b
        new Thread(new Runnable() {
            @Override
            public void run(a) {
                sThreadLocal.set("This is in thread B");
                System.out.println("Thread name:" + Thread.currentThread().getName() + "-"+ sThreadLocal.get()); }},"Thread b").start();
        C / / thread
        new Thread(() -> {
            sThreadLocal.set("This is in thread C");
            System.out.println("Thread name:" + Thread.currentThread().getName() + "-" + sThreadLocal.get());
        }, Thread "c").start(); }}Copy the code

The output is as follows

Thread name: main-- this is the thread name in the main thread: thread B -- this is the thread name in thread B -- this is the thread name in thread A -- this is the thread name in thread C -- this is the Process finished with exit code in thread C0
Copy the code

You can see that each thread accesses data from its own ThreadLocalMap through ThreadLocal without any dirty reads. This is because each thread already stores a key-value pair that ThreadLocal is the Key variable copy of Vaule. (Quarantined)

How does a ThreadLocal copy a variable into a Thread’s ThreadLocalMap?

Let’s keep talking…

When we initialize a thread, we create a Map container with ThreadLocalMap inside it to be used.

public class Thread implements Runnable {
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;
}
Copy the code

When ThreadLocalMap is created and loaded, its static inner class Entry is loaded, completing the initialization action.

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

At this point, the Map inside the Thread is initialized, and how it interacts with the ThreadLocal and how the ThreadLocal manages the key-value pair relationships.

ThreadLocal analysis

Let’s take a look at the internal logic of its core approach and answer the questions above:

  • The set() method is used to hold the replica variable value of the current thread.

  • The get() method is used to get the copy variable value of the current thread.

  • InitialValue () is the initial copy variable value of the current thread.

  • The remove() method removes the replica variable value of the current thread.

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);
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}
Copy the code

Explain it to you:

When we call the set method inside Thread:

  • The first step is to getThread that calls the current method.
  • Then let nature take its courseInternal threadtheThreadLocalMapThe container.
  • And then finally I put the variablesA copy of theThrow it in.

No… See, ThreadLocal (think of it as a tool for maintaining variables inside threads!) ·ThreadLocalMap is used to copy variables into the Map container of the Thread. The Key is the current ThreadLocal and the Value is the copy of the variable.

The get method

/**
 * 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(a) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if(map ! =null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if(e ! =null)
            return (T)e.value;
    }
    return setInitialValue();
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

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;
}

protected T initialValue(a) {
    return null;
}
Copy the code
  • Gets the ThreadLocalMap object for the current thread
  • Get the thread store Entry node from the map based on this (the current ThreadLocal object).
  • The Value of the corresponding copy of Value obtained from the Entry node is returned.
  • If map is empty, the initial value is null, that is, the thread variable copy is null.

The remove method

Clear KV in Map

/**
 * Removes the current thread's value for this thread-local
 * variable.  If this thread-local variable is subsequently
 * {@linkplain #get read} by the current thread, its value will be
 * reinitialized by invoking its {@link #initialValue} method,
 * unless its value is {@linkplain#set set} by the current thread * in the interim. This may result in multiple invocations of the * <tt>initialValue</tt>  method in the current thread. * *@since1.5 * /
public void remove(a) {
 ThreadLocalMap m = getMap(Thread.currentThread());
 if(m ! =null)
     m.remove(this);
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

 /** * Remove the entry for key. */
    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 (e.get() == key) {
                  e.clear();
                  expungeStaleEntry(i);
                  return; }}}Copy the code

Let’s look at ThreadLocalMap again, something that actually stores (isolates) data.

ThreadLocalMap

ThreadLocalMap is an internal class of ThreadLocal that implements its own Map structure.

The Entry uses k-V to organize the data, and the key in the Entry is a ThreadLocal object and is a weak reference (a weak reference whose lifetime only lasts until the next GC).

We’ll talk about weak references at the end.

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

Member variables of ThreadLocalMap

static class ThreadLocalMap {
    /** * 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;

    /** * The number of entries in the table. */
    private int size = 0;

    /** * The next size value at which to resize. */
    private int threshold; // Default to 0
}
Copy the code

HashCode calculation

ThreaLocalMap does not call ThreadLocal hashcode (inherited from object hashCode). Instead, it calls NexthashCode.

private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode = new AtomicInteger();
 //1640531527 Evenly distributes hash slots
private static final int HASH_INCREMENT = 0x61c88647; 
private static int nextHashCode(a) {
      return nextHashCode.getAndAdd(HASH_INCREMENT);
}
Copy the code

Hash conflict

The biggest difference from HashMap is that ThreadLocalMap resolves Hash collisions by simply adding or subtracting steps and linear probing to find the next adjacent position.

/** * Increment i modulo len. */
private static int nextIndex(int i, int len) {
    return ((i + 1 < len) ? i + 1 : 0);
}

/** * Decrement i modulo len. */
private static int prevIndex(int i, int len) {
    return ((i - 1> =0)? i -1 : len - 1);
}
Copy the code

ThreadLocalMap is inefficient in resolving Hash collisions by linear probing, such as when a large number of different ThreadLocal objects are put into the map. Therefore, it is recommended to store only one variable (one ThreadLocal) per thread to avoid Hash collisions. If a thread wants to store multiple set variables, it needs to create multiple ThreadLocal, which will greatly increase the possibility of Hash collisions when put into a Map.

Is that clear? When you need to store multiple variables in a thread, do you expect multiple sets? You’re wrong. You have to create multiple ThreadLocal’s. Multiple sets fail the purpose of storing multiple variables.

sThreadLocal.set("This is in thread A");
Copy the code

Weak reference problem for Key

Look at gobbledygook, why use weak quotes.

To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys. To handle very large and very long threads, hash tables use weak references as keys.

  • Long life cycle: Think of threads in a thread pool for a moment

If a ThreadLocal is not strongly referenced by an external object such as Thread, the weak reference Key will be reclaimed during GC, and the strong reference Value will not be reclaimed. If the Thread that created the ThreadLocal keeps running, such as the Thread in the Thread pool, In this case, the value in the Entry object may not be collected and memory leaks may occur.

  • If the key uses a strong reference: The object referenced by the ThreadLocal is reclaimed, but ThreadLocalMap still holds a strong reference to the ThreadLocal. Without manual deletion, the ThreadLocal will not be reclaimed, resulting in Entry memory leaks.

  • Key uses weak references: The object referenced by ThreadLocal is reclaimed, and since ThreadLocalMap holds a weak reference to ThreadLocal, ThreadLocal is reclaimed even without manual deletion. The value is cleared the next time ThreadLocalMap calls set,get, and remove.

Java8 has implemented some optimizations. For example, when the get(), set(), and remove() methods of ThreadLocal are called, the entire Entry of ThreadLocalMap will be set to NULL. Facilitate the next memory reclamation.

In Java8, the for loop iterates through the entire Entry array and replaces any key=null to avoid memory leaks.

       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;
                        while(tab[h] ! =null) h = nextIndex(h, len); tab[h] = e; }}}return i;
        }
Copy the code

The lifetime of a ThreadLocalMap is usually the same as that of a Thread. If you do not manually remove the corresponding key, it will cause a memory leak. Using weak references, however, provides an additional layer of protection: weak references to ThreadLocal will be collected by the GC without leaking memory, and the corresponding value will be cleared the next time ThreadLocalMap calls set, GET, and remove. Java8 has already optimized this code.

conclusion

  • Each ThreadLocal can hold only one copy of a variable. If you want a thread to hold more than one copy, you need to create multiple ThreadLocal.
  • The ThreadLocalMap key inside a ThreadLocal is a weak reference and runs the risk of a memory leak.
  • Each time ThreadLocal is used, its remove() method is called to clean up the data.