Prior to Java1.4, ThreadLocals would cause contention between threads. In the new design, each thread has its own ThreadLocalMap to improve throughput, however, we still face the possibility of memory leaks because the values in the ThreadLocalMap of long-running threads are not cleared

In earlier versions of Java, ThreadLocals had problems competing when accessed by multiple threads, making them virtually useless in multi-core applications. In Java 1.4, a new design has been introduced where designers store ThreadLocals directly in threads. When we now call ThreadLocal’s get method, we will return an instance of ThreadLocalMap (an inner class of ThreadLocal) from the current thread.

When a thread exits, it deletes all the values in its ThreadLocal. This happens in the exit() method before garbage collection, and if we forget to call the remove() method after using ThreadLocal, the value will remain when the thread exits.

ThreadLocalMap contains weak references to ThreadLocal as well as strong references to values. However, it does not determine which weak reference values in the ReferenceQueue have been cleared because Entry cannot be cleared immediately from ThreadLocalMap.

From the point of view of threads, each Thread holds a reference to an instance of ThreadLocalMap. ThreadLocalMap is the local variable space of a Thread, storing its data as follows:

Entry

Entry inherits WeakReference class and is a data structure that stores thread private variables. A ThreadLocal instance is used as a reference, which means that if the ThreadLocal instance is null, the corresponding Entry can be removed from the table.

class Entry extends WeakReference<ThreadLocal<? >>{ Object value; Entry(ThreadLocal<? > k, Object v) {super(k); value = v; }}Copy the code

ThreadLocalMap

The default size is INITIAL_CAPACITY(16). Here are a few parameters:

Size: number of elements in the table. Threshold: 2/3 of the size of the table. When size >= threshold, the table is traversed and the element whose key is null is deleted. If size >= threshold*3/4, the table needs to be expanded.

ThreadLocal. The set () implementation

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

See this in the code above:

Gets the ThreadLocalMap instance from the current Thread Thread. ThreadLocal instances and values are encapsulated as entries. Let’s see how Entry is stored into a table array:

private void set(ThreadLocal
        key, Object value) {
    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

1. Generate hash values using the nextHashCode method of ThreadLocal.

private static AtomicInteger nextHashCode = new AtomicInteger();
private static int nextHashCode(a) {    
    return nextHashCode.getAndAdd(HASH_INCREMENT);
}


Copy the code

As you can see from the nextHashCode method, the hash value of ThreadLocal increases atomic HASH_INCREMENT each time it is instantiated.

2. Use hash & (len-1) to locate position I in the table. Assume that the element at position I in the table is f. 3. If F! = null, assuming the reference in f is k:

  • If k is the same as the current ThreadLocal instance, change the value and return.
  • If k is null, f is a stale element. Call the replaceStaleEntry method to remove all old elements from the table (that is, the entry reference is null) and insert a new element.
  • Otherwise, find the next element f using the nextIndex method and proceed to Step 3. If f == null, add the Entry to position I of the table. Remove stale elements with cleanSomeSlots. If there are no elements removed from the table, determine whether to expand in the current situation.

4. If f == null, add the Entry to position I of the table. 5. Remove stale elements through cleanSomeSlots. If no element is deleted in the table, determine whether to expand the current situation.

Table capacity

If the number of elements in the table reaches 3/4 of the threshold, the capacity expansion is performed. The process is simple:

private void resize(a) {
    / / the old array
    Entry[] oldTab = table;
    
    // The old array length
    int oldLen = oldTab.length;
    // New array length = old array length *2
    int newLen = oldLen * 2;
    / / the new array
    Entry[] newTab = new Entry[newLen];
    / / count
    int count = 0;

    for (int j = 0; j < oldLen; ++j) {
        Entry e = oldTab[j];
        if(e ! =null) { ThreadLocal<? > k = e.get();if (k == null) {
                e.value = null; // Help the GC
            } else {
                int h = k.threadLocalHashCode & (newLen - 1);
                while(newTab[h] ! =null)
                    h = nextIndex(h, newLen);
                newTab[h] = e;
                count++;
            }
        }
    }

    setThreshold(newLen);
    size = count;
    table = newTab;
}


Copy the code

1. Create a new array newTab that is twice the size of the original array. 2. Copy the elements of the table to newTab, ignoring old elements. Assume that the element e in the table needs to be copied to position I of newTab.

ThreadLocal. The get () implementation

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; }}return setInitialValue();
}

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

Gets the threadLocals of the current thread.

If threadLocals is not null, the corresponding entry is found through threadLocalmap. getEntry. If the reference is the same as the current key, it is returned; otherwise, matching continues in the rest of the table. If threadLocals is null, it is initialized by the setInitialValue method and returns.

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

The magic number 0 x61c88647

  • Generating hash code gaps for this magic number allows the generated values, or ThreadLocal ids, to be evenly distributed in an array of powers of two.
private static final int HASH_INCREMENT = 0x61c88647;

private static int nextHashCode(a) {
   return nextHashCode.getAndAdd(HASH_INCREMENT);
}
Copy the code
  • As you can see, it adds a magic number 0x61C88647 to the ID/threadLocalHashCode of the last constructed ThreadLocal.
  • The magic number is chosen in relation to the Fibonacci hash, and 0x61C88647 corresponds to decimal 1640531527.
  • The multiplier of the Fibonacci hash can be given by (long) ((1L << 31) * (math.sqrt (5) -1)) to get 2654435769, or if converted to a signed int, -1640531527. In other words (1L << 32) – (long) ((1L << 31) * (math.sqrt (5) -1)) is 1640531527 or 0x61C88647.
  • In theory and practice, when we assign each ThreadLocal its own ID using 0x61C88647 as the magic sum, that is, threadLocalHashCode modulo with the power of 2, the results are evenly distributed.
  • ThreadLocalMap uses linear detection, and the advantage of uniform distribution is that the next available slot can be detected quickly, thus ensuring efficiency. To optimize efficiency.

ThreadLocal and memory leaks

  • The reason for the discussion about memory leakage is that in the scenario with thread reuse such as thread pool, the lifetime of a thread is very long, and the long-term non-collection of large objects affects the efficiency and security of system operation. If threads are not reused, they will be destroyed without causing memory leaks

  • When we read the source code for ThreadLocalMap, we can conclude that it is a good coding practice to explicitly remove a ThreadLocal without causing a memory leak.

  • If you must use ThreadLocal, be sure to remove the value immediately after you have done so, and preferably before returning the thread to the thread pool. The best practice is to use remove () instead of set (null), as this causes the WeakReference to be removed immediately, along with the value.