[Source Code] threadlocal source code super detailed explanation

what is threadlocal

Threadlocal can be understood as a local variable of the thread itself, which can only be accessed by the thread itself, and each thread will maintain its own threadlocal.

how to use

The method of use is very simple, the core is two methods set/get

public class TestThreadLocal {
    
    

    private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) {
    
    
        new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                try {
    
    
                    threadLocal.set("aaa");
                    Thread.sleep(500);
                    System.out.println("threadA:" + threadLocal.get());
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        }).start();
        new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                threadLocal.set("bbb");
                System.out.println("threadB:" + threadLocal.get());
            }
        }).start();
    }

}

operation result

threadB:bbb
threadA:aaa

Through the above code, we can see that two threads use a threadlocal. The first thread sets the value before the second thread, and the second thread gets before the first thread. You can see that the obtained value is not affected. , which proves that each thread can only get the variables of this thread.

Application Scenario

In what scenarios can it be used?

  1. In a multi-tenant system, you can parse the tenant id in the global pre-interceptor and put it in threadlocal and encapsulate a util. The controller/service can be obtained directly from TenementUtil.get() without repeated parsing or each The controller method has to parse the tenant id again, resulting in a lot of code redundancy
  2. The same logic can put the user's information analysis into threadlocal in the global pre-interceptor
  3. If you have read my xxl-job source code article , you should know that the context object encapsulated by the xxl-job executor is also stored in threadlocal, so we only need to focus on the business logic

How does ThreadLocal make a thread have multiple ThreadLocal objects?

Variables Threadare maintained in each classthreadLocals

ThreadLocal.ThreadLocalMap threadLocals = null;

Let's look at ThreadLocal.ThreadLocalMapthe constructor

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    
    
    // 初始化entry表
    table = new Entry[INITIAL_CAPACITY];
    // 计算table表的位置
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    table[i] = new Entry(firstKey, firstValue);
    // 当前size
    size = 1;
    // 设置阈值
    setThreshold(INITIAL_CAPACITY);
}

It can be seen that threadlocalmap maintains a table array, and the location of the array is threadLocalHashCodeguaranteed by ThreadLocal

Let's look at the logic of threadLocalHashCode and locate ThreadLocalthis code

// 本对象私有常量
private final int threadLocalHashCode = nextHashCode();
// 共享原子对象
private static AtomicInteger nextHashCode =
    new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;
private static int nextHashCode() {
    
    
  	// 获取并增加,原子操作
    return nextHashCode.getAndAdd(HASH_INCREMENT);
}

It can be seen that each ThreadLocal object maintains a final threadLocalHashCode, and the constant is obtained through the static method AtomicInteger.getAndAdd. AtomicInteger is an atomic operation based on cas, so there will be no duplication under concurrent creation. In this case, the threadLocalHashCode of each of our ThreadLocal objects can be guaranteed to be absolutely unique. That is to say, each new ThreadLocal has a different threadLocalHashCode value.

ThreadLocal memory leak problem

When it comes to memory leaks, we have to understand the storage structure and references of threadlocal first. Let's take a look at ThreadLocalMapthe construction method first.

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    
    
    // 初始化entry表
    table = new Entry[INITIAL_CAPACITY];
    // 计算table表的位置
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    table[i] = new Entry(firstKey, firstValue);
    // 当前size
    size = 1;
    // 设置阈值
    setThreshold(INITIAL_CAPACITY);
}

You can see that objects are used for storage here Entry, let's take a look at its structure

static class Entry extends WeakReference<ThreadLocal<?>> {
    
    
    
    Object value;

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

You can see that the Entry object inherits WeakReference(weak reference) objects, why use weak references here?

We can take a look at the ThreadLocal reference structure through the figure below. The dotted line is a weak reference, and the solid line is a strong reference. If you don’t understand strong references and weak references, you can read my previous article. Reference types in JAVA, strong references, soft references, weak references, virtual references
insert image description here

It can be seen that ThreadLocal is the key of Entry, which is a weak reference. When there is no external strong reference to the ThreadLocal object, the ThreadLocal will be recycled the next time gc recycles.

During use, it is recommended to put ThreadLocal in the member variable position, and try to use static decoration to avoid frequent creation of ThreadLocal instances.

Be careful when storing large objects. If it is not necessary, try not to store large objects. If it is absolutely necessary, it is recommended to call the method to removeremove the object after use to avoid heap memory overflow.

When the thread ends, the ThreadLocalMap and Entry tables will be recycled.

set(T value) method

turn upjava.lang.ThreadLocal#set

public void set(T value) {
    
    
    // 获得当前线程实例
    Thread t = Thread.currentThread();
    // 获得存储对象
    ThreadLocalMap map = getMap(t);
    if (map != null) {
    
    
        // 设置value
        map.set(this, value);
    } else {
    
    
        // 创建并设置
        createMap(t, value);
    }
}

When set, it will get the current object, and try to get the threadLocalMap object from the Thread object, if not, create it

Let 's see getMaphowcreateMap

ThreadLocalMap getMap(Thread t) {
    
    
    return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
    
    
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

It can be seen that the threadLocalMap object is maintained by the Thread object, which means that each thread has an independent threadLocalMap object. When the threadLocals object of Thread is empty, the ThreadLocalMap object will be created.

Next, let's see how the value is setjava.lang.ThreadLocal.ThreadLocalMap#set

private void set(ThreadLocal<?> key, Object value) {
    
    
    Entry[] tab = table;
    int len = tab.length;
    // 计算数组下标
    int i = key.threadLocalHashCode & (len-1);

    // 找到相同的threadlocal,如果找不索引加一继续找
    // 此处跳出有两个条件,找到相同的threadlocal,或者找k为null
    // 此处主要作用就是看数组中是否存在threadlocal,存在就覆盖赋值
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
    
    
        ThreadLocal<?> k = e.get();

        if (k == key) {
    
    
            // 如果找到threadlocal就直接修改值并结束set
            e.value = value;
            return;
        }

        if (k == null) {
    
    
            // 不存在需要替换过期值
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    // 代码走到这里说明没有找到threadlocal,也不存在过期值,那么直接给索引位置设置entry
    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        // 扩容动作
        rehash();
}

Pay attention here replaceStaleEntry, the code goes here to indicate that there is an expired value. We said earlier that threadlocal is weakly referenced by entry. When there is no strong reference in the main thread, it will naturally be recycled by gc. At this time, there is a situation where the entry object exists , but the key value does not exist, then this method will be called to replace the data,

Adopted 开放地址法, when the subscript conflicts, go to find the next empty position

Let me explain the above code with the following figure. Suppose I have entry1-5. Now entry1, entry2, entry3, and entry4 are stored in the table. Now I want to store entry5, and the calculated subscript is 2. Finally, entry5 is actually stored in the subscript 3

image-20220416105550366

image-20220416105816260

Now after the key of entry3 (that is, threadlocal) loses its strong reference, its entry will still be stored in the table array, and will be replaced when the calculated index is at its location.

image-20220416110058336

Let's look at the replacement codejava.lang.ThreadLocal.ThreadLocalMap#replaceStaleEntry

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

        // 找到相同的key则需要进行置换动作
        if (k == key) {
    
    
            // 置换value 和key
            e.value = value;

            tab[i] = tab[staleSlot];
            tab[staleSlot] = e;

            // 如果第一层循环没有找到任何对象,需要将数据对其
            if (slotToExpunge == staleSlot)
                slotToExpunge = i;
            // 清理过期entry 结束置换动作
            cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
            return;
        }
        // 如果key不存在,且前面不存在失效的key,则将数据对其
        if (k == null && slotToExpunge == staleSlot)
            slotToExpunge = i;
    }

    // 没有找到相同的key则直接new一个新的entry放进去
    tab[staleSlot].value = null;
    tab[staleSlot] = new Entry(key, value);

    // 清楚其他的过期对象
    if (slotToExpunge != staleSlot)
        cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}

It can be seen that when there is an expired situation, it will not be cleared directly, but the previous entry object will be reused. When the entry does not exist, a new entry will be created.

get() method

Through the previous content, we have understood the operation principle and reference structure of the entire threadlocal. Next, we will analyze the get method in depth.

java.lang.ThreadLocal#get

public T get() {
    
    
    // 获得当前线程
    Thread t = Thread.currentThread();
    // 获得threadlocalmap对象
    ThreadLocalMap map = getMap(t);
    if (map != null) {
    
    
        // 获得entry对象
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
    
    
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            // 正常返回
            return result;
        }
    }
    // 当threadlocalmap不存在时 初始化threadlocalmap并返回
    return setInitialValue();
}

java.lang.ThreadLocal.ThreadLocalMap#getEntry

private Entry getEntry(ThreadLocal<?> key) {
    
    
    // 计算entry table索引
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    if (e != null && e.get() == key)
        return e;
    else
        // 当entry的值不存在时
        return getEntryAfterMiss(key, i, e);
}

java.lang.ThreadLocal.ThreadLocalMap#getEntryAfterMiss

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)
            // 找到entry
            return e;
        if (k == null)
            // 移除过期条目
            expungeStaleEntry(i);
        else
            // 向下扫描
            i = nextIndex(i, len);
        e = tab[i];
    }
    return null;
}

Why not use hashmap directly

Under normal circumstances, the amount of data stored in ThreadLocal is not too large. After being removed, it will be recycled by the garbage collector. The data structure is just an array. Using this storage method saves space and the query efficiency of array subscripts is also higher.

Data structure:
hashmap is an array + linked list + red-black tree
threadlocalmap is only an array

Reference type:
hashmap value is a strong reference, which is not conducive to memory release.
threadlocalmap is a weak reference, and the memory performance is better

hash conflicts:
hashmap resolves conflicts through linked lists/red-black trees
threadlocalmap resolves conflicts through open addressing

In terms of performance:
hashmap has better performance with a large amount of data,
threadlocalmap has better performance with a small amount of data

Guess you like

Origin blog.csdn.net/qq_21046665/article/details/124211071