preface

ThreadLocal can be seen in many places in Java concurrent programming, where it is used to address thread safety, that is, to implement multithreaded isolation. Today, the author mainly analyzes the learning process of Threadlocal.

What is ThreadLocal?

To understand how this thing works, you have to know what it is, right?

A ThreadLocal is called a thread variable, meaning that the variable filled with a ThreadLocal belongs to the current thread and is isolated from other threads, meaning that the variable is unique to the current thread.

ThreadLocal creates a copy of a variable in each thread, so that each thread can access its own internal copy of the variable.

With the basic concept in mind, let’s look at an example:

public class Demo {
    // Define a global ThreadLocal variable
    public static final ThreadLocal<String> STRING_THREAD_LOCAL = ThreadLocal.withInitial(() -> "HELLO");

    public static void main(String[] args) throws InterruptedException {
        System.out.println(Thread.currentThread().getName() + "-->start:" +STRING_THREAD_LOCAL.get());

        Thread t1 = new Thread(() -> {
            String value =  STRING_THREAD_LOCAL.get();
            if(Objects.nonNull(value) && value.equals("HELLO")){
                STRING_THREAD_LOCAL.set("WORLD");
            }
            System.out.println(Thread.currentThread().getName() + "-- >" + STRING_THREAD_LOCAL.get());
        },"t1");

        t1.start();
        t1.join();

        System.out.println(Thread.currentThread().getName() + "-->end:"+STRING_THREAD_LOCAL.get()); }}Copy the code
  • Let’s take a look at the results:

You can see that the value of the main thread has not changed, while the value of thread T1 has changed.

2. Analysis of ThreadLocal principle

Now that we’ve seen from the examples above, threadLocal can isolate multiple threads so that each thread’s copy of the shared variable changes is visible only to itself. Resolved the issue of sharing between threads

2.1 Method Analysis

If you take a look at the official set source code, you’ll find a key one: ThreadLocalMap

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

It seems that before we get to the set method, we need to look at ThreadLocalMap

2.2 ThreadLocalMap

ThreadLocalMap is actually a member variable maintained by each thread.

static class ThreadLocalMap {


    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

ThreadLocalMap contains an array of entries that inherit weak references.

The key of a ThreadLocalMap is a weak reference to the instance of ThreadLocal, and the value is the initial value of the ThreadLocal or the value set by the thread.

2.3 set method

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

The set method simply sets a value in the current thread and stores it in ThreadLocalMap.

The createMap method initializes the Map and sets its value while getMap is empty

  • ThreadLocalMap is not initialized
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue); } ThreadLocalMap(ThreadLocal<? > firstKey, Object firstValue) {// Initialize a table array of length 16
    table = new Entry[INITIAL_CAPACITY];
    // Calculate index subscript through routing algorithm
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    table[i] = new Entry(firstKey, firstValue);
    size = 1;
    setThreshold(INITIAL_CAPACITY);
}
Copy the code
  • ThreadLocalMap has been initialized

If it has already been initialized, call threadLocalmap.set () and keep it.

private void set(ThreadLocal
        key, Object value) {
    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);
    // Iterate through the Entry array based on index indices
    for(Entry e = tab[i]; e ! =null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<? > k = e.get();// If the key is equal, the original value is overwritten
        if (k == key) {
            e.value = value;
            return;
        }
        // If key is empty, overwrite key and value.
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    tab[i] = new Entry(key, value);
    int sz = ++size;
    // If the threshold is exceeded, expand the capacity
    if(! cleanSomeSlots(i, sz) && sz >= threshold)//for loop, rehash();
        rehash();
}
Copy the code

2.4 Hash Conflict in ThreadLocalMap

ThreadLocalMap’s set method is similar to HashMap’s PUT method. But there was also a problem:

ThreadLocalMap does not have a list structure, so how can hash conflicts be resolved?

  • Calculates the index subscript I of the current element store based on the key.
  • tab[i] ! = null
    • If the key stored in TAB [I] is different from the current key, search for the empty key
    • If TAB [I] stores the same keys but different values, the values are updated
    • If TAB [I] holds a null key, the ThreadLocal instance may have been reclaimed.
  • If TAB [I] == null, store the key and value directly.

In plain English, this is a linear for loop that iterates over empty positions to resolve hash conflicts.

2.5 Replace and clear the replaceStaleEntry method

When key==null, the replaceStaleEntry method is used to override the null key and value

private void replaceStaleEntry(ThreadLocal<? > key, Object value,int staleSlot) {
    Entry[] tab = table;
    int len = tab.length;
    Entry e;

    int slotToExpunge = staleSlot;
    for (inti = prevIndex(staleSlot, len); (e = tab[i]) ! =null;
         i = prevIndex(i, len))
        if (e.get() == null)
            slotToExpunge = i;

    for (inti = nextIndex(staleSlot, len); (e = tab[i]) ! =null; i = nextIndex(i, len)) { ThreadLocal<? > k = e.get();if (k == key) {
            e.value = value;

            tab[i] = tab[staleSlot];
            tab[staleSlot] = e;

            if (slotToExpunge == staleSlot)
                slotToExpunge = i;
            cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
            return;
        }

        if (k == null && slotToExpunge == staleSlot)
            slotToExpunge = i;
    }

    tab[staleSlot].value = null;
    tab[staleSlot] = new Entry(key, value);

    if(slotToExpunge ! = staleSlot) cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); }Copy the code

Other issues

3.1 Memory Leakage

Memory leak: Simply put, the space opened is not released after the completion of use, resulting in memory has been occupied.

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

Threadlocal stores its weakly referenced instance object as a key in the ThreadLocalMap. This causes the key to be reclaimed in the absence of strong external references, whereas if the thread that created the Threadlocal keeps running, The value in the Entry object may cause a memory leak (which can never be collected).

  • Solutions:

When using Threadlocal, use the remove() method at the end of the code.

3.2 a weak reference

Weak references: Non-required memory. When the JVM does garbage collection, objects associated with weak references are reclaimed regardless of whether memory is sufficient. In Java, using Java. Lang. Ref. The WeakReference class

In fact, the key is designed to be a weak reference: also to prevent memory leaks.