Home > Java > javaTutorial > Detailed explanation and source code analysis of ThreadLocal in Java programming

Detailed explanation and source code analysis of ThreadLocal in Java programming

WBOY
Release: 2023-04-21 15:19:08
forward
1574 people have browsed it

    Introduction

    ThreadLocal provides a way so that in a multi-threaded environment, each thread can have its own unique data and can Passed from top to bottom throughout the execution of the thread.

    1. Usage demonstration

    Maybe many students have not used ThreadLocal. Let’s first demonstrate the usage of ThreadLocal. The demo is as follows:

    /**
     * ThreadLocal 中保存的数据是 Map
     */
    static final ThreadLocal<Map<String, String>> context = new ThreadLocal<>();
    @Test
    public void testThread() {
      // 从上下文中拿出 Map
      Map<String, String> contextMap = context.get();
      if (CollectionUtils.isEmpty(contextMap)) {
        contextMap = Maps.newHashMap();
      }
      contextMap.put("key1", "value1");
      context.set(contextMap);
      log.info("key1,value1被放到上下文中");
    	// 从上下文中拿出刚才放进去的数据
      getFromComtext();
    }
    private String getFromComtext() {
      String value1 = context.get().get("key1");
      log.info("从 ThreadLocal 中取出上下文,key1 对应的值为:{}", value1);
      return value1;
    }
    //运行结果:
    demo.ninth.ThreadLocalDemo - key1,value1被放到上下文中
    demo.ninth.ThreadLocalDemo - 从 ThreadLocal 中取出上下文,key1 对应的值为:value1
    Copy after login

    As you can see from the running results , the value corresponding to key1 has been obtained from the context.

    The getFromComtext method does not accept any input parameters. Through this line of code, context.get().get("key1"), the value of key1 is obtained from the context. Next, let's take a look at ThreadLocal. How the underlying layer implements context transfer.

    2. Class structure

    2.1. Class generics

    ThreadLocal defines the class with generics, indicating that ThreadLocal can store data in any format. The source code is as follows:

    public class ThreadLocal<T> {}
    Copy after login

    2.2. Key attributes

    ThreadLocal has several key attributes. Let’s take a look at them one by one:

    // threadLocalHashCode 表示当前 ThreadLocal 的 hashCode,用于计算当前 ThreadLocal 在 ThreadLocalMap 中的索引位置
    private final int threadLocalHashCode = nextHashCode();
    // 计算 ThreadLocal 的 hashCode 值(就是递增)
    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }
    // static + AtomicInteger 保证了在一台机器中每个 ThreadLocal 的 threadLocalHashCode 是唯一的
    // 被 static 修饰非常关键,因为一个线程在处理业务的过程中,ThreadLocalMap 是会被 set 多个 ThreadLocal 的,多个 ThreadLocal 就依靠 threadLocalHashCode 进行区分
    private static AtomicInteger nextHashCode = new AtomicInteger();
    Copy after login
    Copy after login

    There is also another important attribute: ThreadLocalMap. When a thread has multiple ThreadLocal When a container is running, a container is needed to manage multiple ThreadLocals. The role of ThreadLocalMap is to manage multiple ThreadLocals in threads.

    2.2.1, ThreadLocalMap

    ThreadLocalMap itself is a simple Map structure, the key is ThreadLocal, the value is the value saved by ThreadLocal, the bottom layer is the data structure of the array, the source code is as follows:

    // threadLocalHashCode 表示当前 ThreadLocal 的 hashCode,用于计算当前 ThreadLocal 在 ThreadLocalMap 中的索引位置
    private final int threadLocalHashCode = nextHashCode();
    // 计算 ThreadLocal 的 hashCode 值(就是递增)
    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }
    // static + AtomicInteger 保证了在一台机器中每个 ThreadLocal 的 threadLocalHashCode 是唯一的
    // 被 static 修饰非常关键,因为一个线程在处理业务的过程中,ThreadLocalMap 是会被 set 多个 ThreadLocal 的,多个 ThreadLocal 就依靠 threadLocalHashCode 进行区分
    private static AtomicInteger nextHashCode = new AtomicInteger();
    Copy after login
    Copy after login

    From the source code, we can see that ThreadLocalMap is actually a simple Map structure. The bottom layer is an array, with an initialization size and an expansion threshold size. The elements of the array are Entry, the key of Entry is the reference to ThreadLocal, and the value is the value of ThreadLocal. .

    3. How does ThreadLocal achieve data isolation between threads?

    ThreadLocal is thread-safe and we can use it with confidence, mainly because ThreadLocalMap is an attribute of a thread. Let’s take a look at the thread Thread The source code of Exclusive to isolation.

    When the parent thread creates a child thread, it will copy the value of inheritableThreadLocals, but not the value of threadLocals. The source code is as follows: Detailed explanation and source code analysis of ThreadLocal in Java programming

    From above We can see in the figure that when a thread is created, the inheritableThreadLocals attribute value of the parent thread is copied.

    4. Set method Detailed explanation and source code analysis of ThreadLocal in Java programming

    The main function of the set method is to set the value into the current ThreadLocal. If the generic type of the current ThreadLocal is Map, then it is to set the map into the current ThreadLocal. The source code is as follows:

    // set 操作每个线程都是串行的,不会有线程安全的问题
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        // 当前 thradLocal 之前有设置值,直接设置,否则初始化
        if (map != null)
            map.set(this, value);
        // 初始化ThreadLocalMap
        else
            createMap(t, value);
    }
    Copy after login

    The code logic is relatively clear. Let’s take a look at the source code of ThreadLocalMap.set, as follows:

    private void set(ThreadLocal<?> key, Object value) {
        Entry[] tab = table;
        int len = tab.length;
        // 计算 key 在数组中的下标,其实就是 ThreadLocal 的 hashCode 和数组大小-1取余
        int i = key.threadLocalHashCode & (len-1);
     
        // 整体策略:查看 i 索引位置有没有值,有值的话,索引位置 + 1,直到找到没有值的位置
        // 这种解决 hash 冲突的策略,也导致了其在 get 时查找策略有所不同,体现在 getEntryAfterMiss 中
        for (Entry e = tab[i];
             e != null;
             // nextIndex 就是让在不超过数组长度的基础上,把数组的索引位置 + 1
             e = tab[i = nextIndex(i, len)]) {
            ThreadLocal<?> k = e.get();
            // 找到内存地址一样的 ThreadLocal,直接替换
            if (k == key) {
                e.value = value;
                return;
            }
            // 当前 key 是 null,说明 ThreadLocal 被清理了,直接替换掉
            if (k == null) {
                replaceStaleEntry(key, value, i);
                return;
            }
        }
        // 当前 i 位置是无值的,可以被当前 thradLocal 使用
        tab[i] = new Entry(key, value);
        int sz = ++size;
        // 当数组大小大于等于扩容阈值(数组大小的三分之二)时,进行扩容
        if (!cleanSomeSlots(i, sz) && sz >= threshold)
            rehash();
    }
    Copy after login

    Let’s pay attention to several points in the above source code:

    The incrementing AtomicInteger is used as the hashCode of ThreadLocal;

    • The formula for calculating the array index position is: hashCode modulo the array size. Since hashCode continues to increase, different hashCode There is a high probability that the index position of the same array will be calculated (but don’t worry about this, in actual projects, ThreadLocals are very few, and there is basically no conflict);

    • Calculated by hashCode If there is already a value at the index position i, it will start from i and continue to search backward through 1 until it finds an empty index position, and put the current ThreadLocal as the key.

    • Fortunately, when using ThreadLocal in daily work, we often only use 1~2 ThreadLocal, and the probability of calculating duplicate arrays through hash is not very high.

      The strategy for resolving array element position conflicts during set also affects the get method. Let's take a look at the get method together.
    5. Get method

    The get method mainly gets the value stored in the current ThreadLocal from ThreadLocalMap. The source code is as follows:

    public T get() {
        // 因为 threadLocal 属于线程的属性,所以需要先把当前线程拿出来
        Thread t = Thread.currentThread();
        // 从线程中拿到 ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            // 从 map 中拿到 entry,由于 ThreadLocalMap 在 set 时的 hash 冲突的策略不同,导致拿的时候逻辑也不太一样
            ThreadLocalMap.Entry e = map.getEntry(this);
            // 如果不为空,读取当前 ThreadLocal 中保存的值
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        // 否则给当前线程的 ThreadLocal 初始化,并返回初始值 null
        return setInitialValue();
    }
    Copy after login

    Then let’s take a look at the getEntry method of ThreadLocalMap , the source code is as follows:

    // 得到当前 thradLocal 对应的值,值的类型是由 thradLocal 的泛型决定的
    // 由于 thradLocalMap set 时解决数组索引位置冲突的逻辑,导致 thradLocalMap get 时的逻辑也是对应的
    // 首先尝试根据 hashcode 取模数组大小-1 = 索引位置 i 寻找,找不到的话,自旋把 i+1,直到找到索引位置不为空为止
    private Entry getEntry(ThreadLocal<?> key) {
        // 计算索引位置:ThreadLocal 的 hashCode 取模数组大小-1
        int i = key.threadLocalHashCode & (table.length - 1);
        Entry e = table[i];
        // e 不为空,并且 e 的 ThreadLocal 的内存地址和 key 相同,直接返回,否则就是没有找到,继续通过 getEntryAfterMiss 方法找
        if (e != null && e.get() == key)
            return e;
        else
        // 这个取数据的逻辑,是因为 set 时数组索引位置冲突造成的  
            return getEntryAfterMiss(key, i, e);
    }
    Copy after login
    // 自旋 i+1,直到找到为止
    private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
        Entry[] tab = table;
        int len = tab.length;
        // 在大量使用不同 key 的 ThreadLocal 时,其实还蛮耗性能的
        while (e != null) {
            ThreadLocal<?> k = e.get();
            // 内存地址一样,表示找到了
            if (k == key)
                return e;
            // 删除没用的 key
            if (k == null)
                expungeStaleEntry(i);
            // 继续使索引位置 + 1
            else
                i = nextIndex(i, len);
            e = tab[i];
        }
        return null;
    }
    Copy after login

    get The comments in the logic source code have been written very clearly, so we will not repeat them again.

    6. Expansion

    When the number of ThreadLocals in ThreadLocalMap exceeds the threshold, ThreadLocalMap will begin to expand. Let’s take a look at the expansion logic:

    //扩容
    private void resize() {
        // 拿出旧的数组
        Entry[] oldTab = table;
        int oldLen = oldTab.length;
        // 新数组的大小为老数组的两倍
        int newLen = oldLen * 2;
        // 初始化新数组
        Entry[] newTab = new Entry[newLen];
        int count = 0;
        // 老数组的值拷贝到新数组上
        for (int j = 0; j < oldLen; ++j) {
            Entry e = oldTab[j];
            if (e != null) {
                ThreadLocal<?> k = e.get();
                if (k == null) {
                    e.value = null; // Help the GC
                } else {
                    // 计算 ThreadLocal 在新数组中的位置
                    int h = k.threadLocalHashCode & (newLen - 1);
                    // 如果索引 h 的位置值不为空,往后+1,直到找到值为空的索引位置
                    while (newTab[h] != null)
                        h = nextIndex(h, newLen);
                    // 给新数组赋值
                    newTab[h] = e;
                    count++;
                }
            }
        }
        // 给新数组初始化下次扩容阈值,为数组长度的三分之二
        setThreshold(newLen);
        size = count;
        table = newTab;
    }
    Copy after login
    Source code The annotations are also relatively clear. We pay attention to two points:

    After expansion, the array size is twice the original array;

    • There is absolutely no size during expansion Thread safety issues, because ThreadLocalMap is an attribute of a thread, and a thread can only operate on ThreadLocalMap at the same time. Because the execution of business logic by the same thread must be serial, then the operation of ThreadLocalMap must also be serial.

    The above is the detailed content of Detailed explanation and source code analysis of ThreadLocal in Java programming. For more information, please follow other related articles on the PHP Chinese website!

    Related labels:
    source:yisu.com
    Statement of this Website
    The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
    Popular Tutorials
    More>
    Latest Downloads
    More>
    Web Effects
    Website Source Code
    Website Materials
    Front End Template