Good morning, new and old readers, my name is Qixi (xī).

What I share with you today is the interview resident guest: ThreadLocal

The goose factory asked about it at the beginning, and the answer to the question is in point 2 of the following text.

1. The underlying structure

The bottom layer of ThreadLocal is composed of an array with a default capacity of 16, k is the reference of the ThreadLocal object, and v is the value to be placed in TheadLocal

 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);
}

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);
}

The array is similar to HashMap. The processing of hash conflict is not handled by linked list/red-black tree, but by the chain address method, that is, the attempt is placed in the next subscript position of the hash conflict subscript in sequence.

The array can also be expanded.

2. How it works

A ThreadLocal object maintains a ThreadLocalMap inner class object, and the ThreadLocalMap object is where key values are stored.

More precisely, the Entry inner class of ThreadLocalMap is where key values are stored

See the source code set() , createMap() .

Because a Thread object maintains a ThreadLocal.ThreadLocalMap member variable, and when the value of ThreadLocal is set, the obtained ThreadLocalMap is the ThreadLocalMap of the current thread object .

 // 获取 ThreadLocalMap 源码
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

Therefore, each thread does not interfere with each other's operations on ThreadLocal, that is, ThreadLocal can achieve thread isolation

3. Use

 ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set("七淅在学Java");
Integer i = threadLocal.get()
// i = 七淅在学Java

4. Why is the bottom layer of ThreadLocal.ThreadLocalMap an array of length 16?

See point 3 for the operation of ThreadLocal. You can see that each set method of ThreadLocal operates on the same key (because it is the same ThreadLocal object, so the key must be the same).

In this way, it seems that the operation of ThreadLocal will only store 1 value. Isn't it good to use an array of length 1? Why use 16 lengths?

Well, in fact, there is a point to pay attention to here, ThreadLocal Can store multiple values

How to store multiple values? See the following code:

 // 在主线程执行以下代码:
ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set("七淅在学Java");
ThreadLocal<String> threadLocal2 = new ThreadLocal<>();
threadLocal2.set("七淅在学Java2");

After executing the code, it looks like 2 ThreadLocal objects are new, but in fact, the data storage is operated on the same ThreadLocal.ThreadLocalMap

Re-emphasized: ThreadLocal.ThreadLocalMap is the place for data access, ThreadLocal is just the API call entry). The truth is in the ThreadLocal class source code getMap()

Therefore, the final result of the above code is that a ThreadLocalMap stores 2 different ThreadLocal objects as keys, and the corresponding value is Qixi is learning Java and Qixi is learning Java2.

Let's look at the set method of ThreadLocal again

 public void set(T value) {
    Thread t = Thread.currentThread();
    // 这里每次 set 之前,都会调用 getMap(t) 方法,t 是当前调用 set 方法的线程
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

// 重点:返回调用 set 方法的线程(例子是主线程)的 ThreadLocal 对象。  
// 所以不管 api 调用方 new 多少个 ThreadLocal 对象,它永远都是返回调用线程(例子是主线程)的 ThreadLocal.ThreadLocalMap 对象供调用线程去存取数据。
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

// t.threadLocals 的声明如下
ThreadLocal.ThreadLocalMap threadLocals = null;

// 仅有一个构造方法
public ThreadLocal() {
}

5. The data is stored in the array, how to solve the problem of hash conflict

Use the chain address method to solve.

How to solve it? Look at the execution of the get and set methods:

  • set:

    • According to the hash value of the ThreadLocal object, locate the position in the ThreadLocalMap array.
    • If there is no element at the position, put it directly at the position
    • if there is an element

      • And the key of the array is equal to the ThreadLocal, then the position element is overwritten
      • Otherwise, find the next empty position until empty or the key is equal.
  • get:

    • According to the hash value of the ThreadLocal object, locate the position in the ThreadLocalMap array.
    • If not, judge the next position
    • Otherwise, take it directly
 // 数组元素结构
Entry(ThreadLocal<?> k, Object v) {
    super(k);
    value = v;
}

6. The hidden memory leak of ThreadLocal

Three prerequisites:

  • ThreadLocal objects maintain a ThreadLocalMap inner class
  • The ThreadLocalMap object maintains an Entry inner class, and this class inherits weak reference WeakReference<ThreadLocal<?>> , which is used to store the ThreadLocal object as the key (see the source code of the Entry construction method at the bottom), and you can see the last source code part.
  • Regardless of whether the current memory space is sufficient or not, the JVM will reclaim the weakly referenced memory during GC

Because ThreadLocal is referenced by the Key variable in Entry as a weak reference, if ThreadLocal has no external strong reference to refer to it, then ThreadLocal will be recycled at the next JVM garbage collection.

At this time, the key in the Entry has been recycled, but the value will not be recycled by the garbage collector because it is a strong reference. In this way, if the thread of ThreadLocal keeps running, the value will not be recovered, resulting in memory leak.

If you want to avoid memory leaks, you can use the remove() method of the ThreadLocal object

7. Why is the key of ThreadLocalMap a weak reference

 static class ThreadLocalMap {
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;

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

Why do we design this way, this is divided into two cases to discuss:

  • The key uses a strong reference: only the thread that created the ThreadLocal is still running, then the key value of the ThreadLocalMap will leak memory, because the life cycle of the ThreadLocalMap is the same as the Thread object that created it.
  • Use weak reference for key: It is a rescue measure, at least the value of weak reference can be GCed in time to reduce memory leaks. Also, even if not manually deleted, the ThreadLocal as the key will be recycled. Because when ThreadLocalMap calls set, get, and remove, it will first judge whether the key corresponding to the previous value is equal to the key currently called. If they are not equal, it means that the previous key has been reclaimed, and the value will also be reclaimed at this time. Therefore, using weak references for keys is the optimal solution.

8. How to share ThreadLocal data (parent and child threads)

  1. When the main thread creates an InheritableThreadLocal object, it creates a ThreadLocalMap for the t.inheritableThreadLocals variable to initialize it. where t is the current thread, the main thread
  2. When creating a child thread, the constructor of Thread will check whether the inheritableThreadLocals of its parent thread is null. It can be seen from step 1 that it is not null, and then the value of the inheritableThreadLocals variable of the parent thread is copied to the child thread.
  3. InheritableThreadLocal overrides getMap, createMap, and uses Thread.inheritableThreadLocals variables

as follows:

 public class InheritableThreadLocal<T> extends ThreadLocal<T> 

关键源码:

第 1 步:对 InheritableThreadLocal 初始化
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

第 2 步:创建子线程时,判断父线程的 inheritableThreadLocals 是否为空。非空进行复制
// Thread 构造方法中,一定会执行下面逻辑
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
    this.inheritableThreadLocals =
        ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

第 3 步:使用对象为第 1 步创建的 inheritableThreadLocals 对象
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }
}

示例:
// 结果:能够输出「父线程-七淅在学Java」
ThreadLocal threadLocal = new InheritableThreadLocal();
threadLocal.set("父线程-七淅在学Java");
Thread t = new Thread(() -> System.out.println(threadLocal.get()));
t.start();

// 结果:null,不能够输出「子线程-七淅在学Java」
ThreadLocal threadLocal2 = new InheritableThreadLocal();
Thread t2 = new Thread(() -> {
    threadLocal2.set("子线程-七淅在学Java");
});
t2.start();
System.out.println(threadLocal2.get());

The article's first public account: Qixi is learning Java , and continues to output Java back-end dry goods.

If it helps you, can you give a like before leaving?


七淅在学Java
312 声望471 粉丝