ThreadLocal may seem a bit strange to some of you, so let’s take a look at it today.

What

A ThreadLocal is a ThreadLocal variable that is filled with variables that belong to the current thread and are isolated from other threads. ThreadLocal creates a copy of a variable in each thread, so that each thread can access its own internal copy of the variable.

How

The main function of ThreadLocal is thread isolation. In The Android messaging mechanism, a very important role of Looper, where each thread has its own Looper, this is implemented through ThreadLocal. Look directly at the code

public final class Looper {
    // sThreadLocal.get() will return null unless you've called prepare().
    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");
        }
        // Setter
        sThreadLocal.set(new Looper(quitAllowed));
    }
  
    /** * Return the Looper object associated with the current thread. Returns * null if the calling thread is not associated with a Looper. */  
    public static @Nullable Looper myLooper(a) {
        // Getter returns the current thread's Looper
        returnsThreadLocal.get(); }}Copy the code

Usage is also fairly simple. The most important things about variables are setters and getters. This allows us to prepare our Looper on any thread, and to retrieve the prepared Looper (in-thread singleton).

Why

Let’s take a look at how variables are isolated between threads behind such a simple interface.

Setter

public class ThreadLocal<T> {
    /**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value. 
     *
     * @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(); 
        // Get the ThreadLocalMap associated with the current thread
        ThreadLocalMap map = getMap(t);
        if(map ! =null)
            map.set(this, value);
        else
          // If the Map is empty, create it and store it as the first value in the Map
            createMap(t, value);
    }
  
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue); }}Copy the code

So what is ThreadLocalMap

static class ThreadLocalMap {
    // use to associate ThreadLocal with its corresponding value
    static class Entry extends WeakReference<ThreadLocal<? >>{
       /** The value associated with this ThreadLocal. */Object value; Entry(ThreadLocal<? > k, Object v) {// Note that the key is ThreadLocal
       k, to which Entry actually holds only a weak reference
           super(k); value = v; }}private Entry[] table;
    
    private void set(ThreadLocal
        key, Object value) {

        Entry[] tab = table;
        int len = tab.length;
        // Use the hash value of TheadLocal to determine where the Entry should be stored
        int i = key.threadLocalHashCode & (len-1);

        for (Entry e = tab[i];// Target positione ! =null; // Loop to target position is empty
             e = tab[i = nextIndex(i, len)]) { // The target position is not available continue to find the next position, at the end of the index jumps to 0ThreadLocal<? > k = e.get();if (k == key) { // Replace the same key
                e.value = value;
                return;
            }

            if (k == null) { 
               // The key is out of date and is recycled. Replace this Entry, and the method will also check and remove other expired entries
                replaceStaleEntry(key, value, i);
                return;
            }
        }

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

As you can see, the ThreadLocalMap implementation is much simpler than the HashMap implementation. It simply holds the combination of keyValues in an array. And if the Index calculated by the hash value already has data, you can simply find the next location. The Hash value is also very simple to calculate:

public class ThreadLocal<T> {
    // Each time a ThreadLocal is new, its hash value increases
    private final int threadLocalHashCode = nextHashCode();
    private static AtomicInteger nextHashCode = new AtomicInteger();
    // The constant to which the hash value is accumulated
    private static final int HASH_INCREMENT = 0x61c88647; 
    /** * Returns the next hash code. * The hash value has nothing to do with itself, only the number of initialized bytes. It feels interesting */
    private static int nextHashCode(a) {
        returnnextHashCode.getAndAdd(HASH_INCREMENT); }}Copy the code

To summarize:

  • Each Thread has a ThreadLocalMap that contains all the ThreadLocal’s for that Thread
  • The map will be loaded lazily when ThreadLocal is set, and the map will place the value of ThreadLocal as the key into the map as the value
  • The hash value of ThreadLocal is used to determine where the value should be placed in the Map

Getter

With that in mind, it’s easier to look at the Get method

public class ThreadLocal<T> {

public T get(a) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if(map ! =null) {
            // Get an entry from a map
            // Index is computed using the hash value of the ThreadLocal. If the key obtained by index is the same as the current ThreadLocal, the target value is returned. Otherwise, the next index is returned until the entry is empty.
            ThreadLocalMap.Entry e = map.getEntry(this);
            if(e ! =null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                returnresult; }}// The value of the default initialization method when no value has been set
        return setInitialValue();
    }

    private T setInitialValue(a) { T value = initialValue(); .// omit the initialization method when map is empty
        map.set(this, value); .return value;
    }
    
    // The default value is null
    protected T initialValue(a) {
        return null; }}Copy the code

Memory leak?

WeakReference<ThreadLocal<? >> Weak references, that is, the ThreadLocal may be reclaimed. If it has been reclaimed, the map will not be able to getValue, and the reference to the value will not be cleared and the value will leak. However, this scenario is still relatively rare, we need to note:

  1. The Setter/Getter methods of ThreadLocalMap contain some logic for cleaning up reclaimed keys
  2. When the key is reclaimed, the lifetime of the Thread may end and the map is reclaimed, so there is no leakage
  3. If the lifetime of a Thread is long, the threadlocal. remove method should be called when the ThreadLocal is used. In this way, the corresponding Entry can be removed from the Map of the Thread to avoid leakage

conclusion

  1. ThreadLocal is used for thread isolation of variables. Calls to get from the same ThreadLocal in different threads return different values
  2. A Thread contains a ThreadLocalMap, which stores ThreadLocal and Value values used by the Thread. Each Thread has its own ThreadLocalMap, which implements variable Thread isolation
  3. A ThreadLocalMap essentially stores data in an array of entries whose key is a weak reference to ThreadLocal and whose value is the value of a ThreadLocal object set
  4. ThreadLocalMap computes indexes using the hash value of ThreadLocal. If an Index conflict occurs, search for the next available Index
  5. ThreadLocal can cause memory leaks, and you can call remove to remove the reference

This article source code based on Android-28