Original address: cmsblogs.com/?p=2442

ThreadLocal is introduced

ThreadLocal provides a way to solve the problem of member variables in multi-threaded environments, but it does not solve the problem of variables shared by multiple threads. So what exactly is a ThreadLocal?

The API looks like this: This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread accessing a variable (through its GET or set methods) has its own local variable, independent of the initialized copy of the variable. ThreadLocal instances are typically private static fields in a class that want to associate state with a thread (for example, a user ID or transaction ID).

Unlike thread synchronization, where multiple threads share the same variable, ThreadLocal creates a separate copy of the variable for each thread, so that each thread can independently change its own copy of the variable without affecting the corresponding copies of other threads.

An example of using ThreadLocal is as follows:

public class SeqCount {

    private static ThreadLocal<Integer> seqCount = new ThreadLocal<Integer>() {
        / / implementation the initialValue ()
        public Integer initialValue(a) {
            return 0; }};public int nextSeq(a) {
        seqCount.set(seqCount.get() + 1);

        return seqCount.get();
    }

    public static void main(String[] args) {
        SeqCount seqCount = new SeqCount();

        SeqThread thread1 = new SeqThread(seqCount);
        SeqThread thread2 = new SeqThread(seqCount);
        SeqThread thread3 = new SeqThread(seqCount);
        SeqThread thread4 = new SeqThread(seqCount);

        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
    }

    private static class SeqThread extends Thread {
        private SeqCount seqCount;

        SeqThread(SeqCount seqCount) {
            this.seqCount = seqCount;
        }

        public void run(a) {
            for (int i = 0; i < 3; i++) {
                System.out.println(Thread.currentThread().getName() + " seqCount :"+ seqCount.nextSeq()); }}}}Copy the code

How ThreadLocal works

The implementation of ThreadLocal looks like this: Each Thread maintains a ThreadLocalMap map whose key is the instance of ThreadLocal itself and whose value is the actual Object that needs to be stored.

That is, ThreadLocal does not store values itself; it simply acts as a key for the thread to retrieve values from ThreadLocalMap. Note the dotted lines in the figure, which indicate that ThreadLocalMap uses a weak reference to ThreadLocal as the Key, and that the weakly referenced object is reclaimed during GC.

ThreadLocal source code analysis

ThreadLocalMap

ThreadLocalMap constructors are as follows:

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

ThreadLocalMap internally uses Entry to store key-values as follows:

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

You can see that the key of the Entry is a ThreadLocal and the value is a value. At the same time, Entry also inherits WeakReference, so the reference of key (ThreadLocal instance) corresponding to Entry is a WeakReference.

Next, look at the core ThreadLocalMap methods set(ThreadLocal> key, Object value) and getEntry().

1, the set (ThreadLocal <? > key, Object value)

private void set(ThreadLocal
        key, Object value) {

        Entry[] tab = table;
        int len = tab.length;
        // Find the position of the element in the array based on the hash value of ThreadLocal
        int i = key.threadLocalHashCode & (len-1);
    	
        for(Entry e = tab[i]; e ! =null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<? > k = e.get();// Key exists, overwrite directly
            if (k == key) {
                e.value = value;
                return;
            }
            // key == null, but there is a value (because e! = null), indicating that the previous ThreadLocal object has been reclaimed
            if (k == null) {
                // Replace old elements with new ones
                replaceStaleEntry(key, value, i);
                return; }}// The key instance corresponding to ThreadLocal does not exist
        tab[i] = new Entry(key, value);
        int sz = ++size;
        // cleanSomeSlots cleans up stale entries (key == null)
        // If stale entries are not cleaned up and the elements in the array are greater than the threshold, rehash
        if(! cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }Copy the code

In addition to storing elements, the set() operation is also important for replaceStaleEntry() and cleanSomeSlots(), which clean up instances of key == NULL to prevent memory leaks. There is one more variable that is important in the set() method: threadLocalHashCode, defined as follows:

private final int threadLocalHashCode = nextHashCode();
Copy the code

ThreadLocalHashCode is a hash of a ThreadLocal, defined as final, which means that a ThreadLocal hash is determined once it has been created. NextHashCode () is used to generate the hash:

private static AtomicInteger nextHashCode = new AtomicInteger();

private static final int HASH_INCREMENT = 0x61c88647;

private static int nextHashCode(a) {
    return nextHashCode.getAndAdd(HASH_INCREMENT);
}
Copy the code

NextHashCode represents the value of threadLocalHashCode allocated to the next ThreadLocal instance, and HASH_INCREMENT represents the increment of threadLocalHashCode allocated to the two ThradLocal instances.

2, the getEntry ()

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);
}
Copy the code

Open addressing is used, so the hash of the current key is not exactly the same as the index of the element in the array. First, take a probe number (the hash of the key), return if the corresponding key is the element we are looking for, otherwise call getEntryAfterMiss().

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;
           // When key == null, the expungeStaleEntry() method is called, which handles key == null,
           // It facilitates GC collection and can effectively avoid memory leaks.
            if (k == null)
                expungeStaleEntry(i);
            else
                i = nextIndex(i, len);
            e = tab[i];
        }
        return null;
    }
Copy the code

ThreadLocal core method

Set (T value) : Sets the value of a thread-local variable for the current thread

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if(map ! =null)
        map.set(this, value);
    else
        createMap(t, value);
}

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

If it is not empty, the set() method of ThreadLocalMap is called. The key is the current ThreadLocal. If it is not present, createMap() is called to create it.

Get () : Returns the thread variable corresponding to the current thread

public T get(a) {
    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;
            returnresult; }}// If ThreadLocalMap does not exist, return the initial value.
    return setInitialValue();
}
Copy the code

First, the member variable ThreadLocalMap is obtained by the current thread, then the Entry of the current ThreadLocal is obtained by ThreadLocalMap, and finally the target value result is obtained by the obtained Entry.

InitialValue () : returns the initialValue of this thread-local variable

protected T initialValue(a) {
    return null;
}
Copy the code

This method is called the first time a thread accesses a variable using the GET method, unless the thread previously called the set method, in which case the thread does not call the initialValue method. Typically, this method is called at most once per thread, but it may be called again on subsequent calls to remove and GET.

The default implementation returns NULL, and if a programmer wants a thread-local variable to have a non-NULL initial value, he must subclass ThreadLocal and override this method.

Remove () : removes the value of the current thread local variable

public void remove(a) {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if(m ! =null)
        m.remove(this);
}
Copy the code

The purpose of this method is to reduce memory footprint. Of course, we don’t need to explicitly call this method, because when a thread terminates, its corresponding local variable is garbage collected.

Why does ThreadLocal leak memory

A ThreadLocalMap uses a weak reference to a ThreadLocal as its key. If a ThreadLocal has no external strong reference to it, then the ThreadLocal will be reclaimed during GC. Null key entries appear in ThreadLocalMap, and there is no way to access the values of these null key entries. If the current Thread does not terminate any longer, there will always be a strong reference chain for the values of these entries with null keys: Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value can never be reclaimed, resulting in memory leaks.

Get (),set(), and remove() remove all null-key values from the ThreadLocalMap.

But these passive precautions are no guarantee against memory leaks:

  • Static ThreadLocal extends the lifetime of ThreadLocal and may cause memory leaks.

  • Allocation using ThreadLocal without calling the get(),set(), and remove() methods results in a memory leak.

The root cause of a ThreadLocal memory leak is that since ThreadLocalMap has the same lifetime as Thread, memory leaks occur if the corresponding key is not manually removed, not because of weak references.

Now that you understand the cause and effect of ThreadLocal memory leaks, how can you avoid them?

  • After each useThreadLocal, all call itremove()Method to clear data.

In the case of thread pools, not cleaning up ThreadLocal in a timely manner is not only a memory leak problem, but more importantly, it can lead to problems with business logic. So using ThreadLocal is like unlocking a lock and cleaning it up when you’re done.

The resources

  1. Deep dive into ThreadLocal

  2. Take a closer look at ThreadLocal memory leaks


If you feel that you have gained something after reading it, please click “like”, “follow” and add the official account “Niumi Technology” to read more wonderful history!! :