What is a ThreadLocal

ThreadLocal provides thread-local variables. Unlike normal variables, which can be accessed and modified by any thread, variables created using ThreadLocal can only be accessed by the current thread, meaning that the thread is private and cannot be accessed or modified by any other thread.

ThreadLocal usage

Create:

ThreadLocal<Boolean> threadLocal = new ThreadLocal<>();Copy the code

The set method:

threadLocal.set(false);Copy the code

The get method:

threadLocal.get()Copy the code

Let’s look at a complete example:

Private ThreadLocal<Boolean> ThreadLocal = new ThreadLocal<>(); // Set and access its value threadlocal.set (true) on the main thread, child thread 1, and child thread 2 respectively; Log.e(TAG, "[Thread#main]threadLocal=" + threadLocal.get() ); new Thread("Thread#1"){ @Override public void run() { threadLocal.set(false); Log.e(TAG, "[Thread#1]threadLocal=" + threadLocal.get() ); }}.start(); new Thread("Thread#2"){ @Override public void run() { Log.e(TAG, "[Thread#2]threadLocal=" + threadLocal.get() ); }}.start();Copy the code

In the code above, threadLocal is set to true on the main thread, false on child thread 1, and unset on child thread 2.

In the following output, you can see that even though the same ThreadLocal object is accessed in different threads, they get different values from ThreadLocal.

[Thread#main]threadLocal=true[Thread#1]threadLocal=false[Thread#2]threadLocal=nullCopy the code

The Android application

In Android, the Looper class takes advantage of ThreadLocal to ensure that only one Looper object exists per thread.

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() ! = null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); }Copy the code

ThreadLocal principle

Let’s take a look at the source code to see how ThreadLocal is implemented internally.

Starting with the set method, the main work is

  • Get current thread

  • Gets or the ThreadLocalMap object of the current thread

  • If ThreadLocalMap is not empty, set the value; Otherwise, create a ThreadLocalMap object and 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

GetMap method to get ThreadLocalMap

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

This method gets the threadLocals variable that is actually the Thread object

ThreadLocal.ThreadLocalMap threadLocals = null;Copy the code

If the set method is called for the first time, the ThreadLocalMap object is empty, ThreadLocalMap is created, and its initial value is set.

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

So let’s summarize the design of ThreadLocal:

Each Thread maintains a ThreadLocalMap table whose key is the TreadLocal instance itself and whose value is the actual stored value. ThreadLocalMap is accessed only by the Thread that owns it. Other threads cannot access or modify ThreadLocalMap.

Let’s look at ThreadLocalMap in detail.

The main process for constructing a ThreadLocalMap:

  • Initializes an array of entries

  • The stored index location is computed using hashcode of key (type ThreadLocal)

  • Stores Entry objects at the specified index location

  • Records the number of entries in the array

  • Set the array expansion threshold

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

Here’s the Entry structure:

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

Entry is a static internal class of ThreadLocalMap that inherits WeakReference . From super(k), it can be seen that Entry is a WeakReference to ThreadLocal. In addition, Entry contains a strong reference to value.

ThreadLocal memory leak problem

First, we draw the memory map of object references related to ThreadLocal (solid lines for strong references, dotted lines for weak references) :


The value in the Entry is always attached to a strong reference from ThreadRef. When the current Thread ends, the ThreadRef is not on the stack. Threads, ThreadLocalMap, and values are collected by GC.

However, if you use a thread pool, then the previous thread instance will survive processing for reuse purposes, which is a true memory leak.

To minimize the possibility and impact of memory leaks, ThreadLocal is designed with some safeguards in mind.

The getEntry method:

The Entry is first obtained from the index location and returned if the Entry is not empty and the key is the same, otherwise the getEntryAfterMiss method is called to query the next location.

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

GetEntryAfterMiss method:

During the whole process, if the key is empty, the expungeStaleEntry method is called to erase the Entry (if the value object in the Entry does not have a strong reference, it will be reclaimed naturally).

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

ExpungeStaleEntry method:

private int expungeStaleEntry(int staleSlot) { Entry[] tab = table; int len = tab.length; // Set value to null TAB [staleSlot]. Value = null; // Set entry to NULL TAB [staleSlot] = null; size--; 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 set method:

The set method does the same, setting the value of all entries with null keys to NULL through the replaceStaleEntry method, making the value recyclable. In addition, an Entry with a null key and value is set to NULL using the expungeStaleEntry method in the rehash method to make it retrievable. In this way, ThreadLocal prevents memory leaks.

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

ReplaceStaleEntry method:

private void replaceStaleEntry(ThreadLocal<? > key, Object value, int staleSlot) { Entry[] tab = table; int len = tab.length; Entry e; int slotToExpunge = staleSlot; for (int i = prevIndex(staleSlot, len); (e = tab[i]) ! = null; i = prevIndex(i, len)) if (e.get() == null) slotToExpunge = i; for (int i = 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

However, the above design approach relies on one prerequisite: ThreadLocalMap’s getEntry or set methods must be called.

If this premise is not true, a memory leak will still occur. Therefore, in many cases, you need to manually call the remove method of ThreadLocal to manually remove ThreadLocal that is no longer needed, thus freeing entries and avoiding memory leaks. In addition, the JDK recommends that the ThreadLocal variable be defined as private static, so that the lifetime of ThreadLocal is longer, and ThreadLocal is not recycled during thread execution. This ensures that the Entry value can be accessed at any time through ThreadLocal’s weak reference and then removed to prevent memory leaks.

conclusion

ThreadLocal avoids instance thread-safety problems by implicitly creating instance copies in different threads.

2. The Entry of a ThreadLocalMap refers to a ThreadLocal as a weak reference, avoiding the problem that ThreadLocal objects cannot be reclaimed.

3. Memory leaks can occur if thread pools are used, and the previous thread instances are still alive for reuse purposes.

ThreadLocal safeguards both get and set methods to avoid memory leaks, but only if they are executed. Therefore, in many cases, you also need to manually call ThreadLocal’s remove method to avoid memory leaks.

5. Consider ThreadLocal when some data is thread-scoped and different threads have different copies of the data.

Welcome to pay attention!