ThreadLocal source code reading notes

Posted by bahewitt on Thu, 13 Jan 2022 07:03:27 +0100

1, Function description

ThreadLocal solves the blocking problem of accessing shared variables, and does not need to sacrifice CPU resources like CAS operation. It maintains a variable copy for each thread. When accessing the variables in ThreadLocal, each thread actually accesses the variable copy in its own thread, and the variable copy in this thread is isolated from the variable copy of other threads, They don't affect each other. That is, the variables wrapped in ThreadLocal are thread level variables.

2, Source code interpretation

ThreadLocal saves data through an internal class ThreadLocalMap, takes itself as a key, and starts with the get method.

public T get() {
  // Get current thread
  Thread t = Thread.currentThread();
  // Gets the ThreadLocalMap in the current thread
  ThreadLocalMap map = getMap(t);
  if (map != null) {
      // Use threadlocal as the key to obtain the Entry object in ThreadLocalMap
      ThreadLocalMap.Entry e = map.getEntry(this);
      if (e != null) {
          @SuppressWarnings("unchecked")
          // Gets a copy of the value of the threadlocal package within the thread
          T result = (T)e.value;
          return result;
      }
  }
  return setInitialValue();
}

It is found in the get method that Threadlocal first obtains the current thread, and then uses the current thread as the key to obtain the ThreadLocalMap object. Then the ThreadLocalMap object is only visible to the current thread, and the contents contained in ThreadLocalMap are only visible to the current thread. Check the getMap method:

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

At this time, it is found that ThreadLocalMap is actually saved in the threadLocals variable of Thread class. Check the Thread class code. ThreadLocalMap class variable threadLocals is saved internally, that is, ThreadLocalMap is defined in ThreadLocal class, but actually saved in Thread class.

/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;


Observe the definition of the ThreadLocalMap class

static class ThreadLocalMap {
    // Static internal class, saving key value pairs, and using weak references
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;

        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
}

The main reason for using weak references is to help the jvm with garbage collection (see WeakHashMap)

It can be found from the memory reference diagram that there are two references to ThreadLocal, namely, the user-defined ThreadLocal variable and the key in the Entry. If the key in the Entry holds a strong reference to ThreadLocal, even if the user-defined ThreadLocal variable is set to null, the memory occupied by ThreadLocal cannot be recycled due to the existence of the key, It will cause memory leakage.
The key in the Entry holds a weak reference to ThreadLocal. When the custom ThreadLocal variable is set to null, only the key references ThreadLocal in the java heap. Because of the characteristics of weak reference, if there is no other strong reference connection, it can be recycled, so it will not cause memory leakage.
If you set ThreadLocal to null to help GC, it is found that the ThreadLocal variable can be recycled, but if value is not cleared before, value will always hold a reference, which will cause memory leakage. Therefore, when the ThreadLocal in a thread is used up, you must first call the remove method to clear the value and set the ThreadLocal to null.

After understanding the role of weak reference, continue to look at the getEntry method of ThreadLocalMap class

private Entry getEntry(ThreadLocal<?> key) {
    // Obtain the subscript of the Entry in the table through the hashcode of the threadlocal class
    int i = key.threadLocalHashCode & (table.length - 1);
    // Get Entry object in table
    Entry e = table[i];
    if (e != null && e.get() == key)
        return e;
    else
        return getEntryAfterMiss(key, i, e);
}

Through the above code, it is found that the actual storage structure of ThreadLocalMap is Entry[] table, and the structure of table is hash table. To verify this view, continue to check the set method of ThreadLocalMap class

private void set(ThreadLocal<?> key, Object value) {
    // Get Entry table
    Entry[] tab = table;
    // Get table length
    int len = tab.length;
    // Obtain the subscript position of the Entry in the table through the hashcode of the threadlocal class
    int i = key.threadLocalHashCode & (len-1);
    // If the Entry[i] is not empty, traverse the table from the subscript I
    for (Entry e = tab[i];
        e != null;
        e = tab[i = nextIndex(i, len)]) {
            ThreadLocal<?> k = e.get();
        // threallocal is the same as the key in the entry and directly replaces the value
        if (k == key) {
            e.value = value;
            return;
        }
        // If key is null, set key,value and modify hashcode
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }
    // The Entry[i] is empty. Set the key and value directly
    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

After reading the set method, you have a general grasp of the table structure.

When setting the value, obtain the subscript position to be stored through the hash code of threadlocal and the length of table. The same method is used to obtain the value.

Topics: Java Back-end