• InheritableThreadLocal父子线程变量共享实现原理


    概述

      我们知道ThreadLocal 的设计初衷是为了解决多线程并发导致的线程安全问题,向每一个线程提供一个自己的变量副本,实现变量隔离。那如何在不同线程之间共享变量呢?InheritableThreadLocal为解决此问题而生,使用她可以实现父子线程访问ThreadLocal的值。
      实现变量传递的关键是在类Thread中定义的本地变量inheritableThreadLocals:

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

    案例分析

      下面先提供一个案例,说明InheritableThreadLocal可以实现父子线程间传递变量。

    /**
     * 线程间共享变量
     *
     * @author Wiener
     * @date 2020/10/27
     */
    public class ShareArgsProblem {
        private static void shareArgs() {
            //主线程中赋值
            ThreadLocal<String> threadLocal = new ThreadLocal<>();
            final ThreadLocal itl = new InheritableThreadLocal();
            itl.set("帅得一匹!");
            threadLocal.set("在 ThreadLocal 中存放了值");
            System.out.println(String.format("① 当前线程名称:%s", Thread.currentThread().getName()));
            Thread t = new Thread() {
                @Override
                public void run() {
                    super.run();
                    //子线程中读取
                    System.out.println(String.format("楼兰的胡杨帅么?%s", itl.get()));
                    System.out.println(String.format("拿不到ThreadLocal 存储的值:%s", threadLocal.get()));
                    System.out.println(String.format("② 当前线程名称: %s", Thread.currentThread().getName()));
                }
            };
            t.start();
        }
    
        public static void main(String[] args) {
            shareArgs();
        }
    }
    
    

      执行main函数,输出结果如下:

      在上面的测试用例中,开启两个线程——主线程和子线程t,主线程中创建了两个变量和一个新的线程t,并尝试在线程t中获取本地变量的值。从输出结果可以看得出来,我们拿不到ThreadLocal类型变量 threadLocal 的值,但是,能够准确拿到 InheritableThreadLocal 类型的变量 itl的值。下面分析为什么可以读取出来InheritableThreadLocal 类型的变量的值。

    InheritableThreadLocal共享变量原理分析

      先让我们看看InheritableThreadLocal的寥寥数行源码:

    
    /**
     * This class extends {@code ThreadLocal} to provide inheritance of values
     * from parent thread to child thread: when a child thread is created, the
     * child receives initial values for all inheritable thread-local variables
     * for which the parent has values.  Normally the child's values will be
     * identical to the parent's; however, the child's value can be made an
     * arbitrary function of the parent's by overriding the {@code childValue}
     * method in this class.
     *
     * <p>Inheritable thread-local variables are used in preference to
     * ordinary thread-local variables when the per-thread-attribute being
     * maintained in the variable (e.g., User ID, Transaction ID) must be
     * automatically transmitted to any child threads that are created.
     * @author  Josh Bloch and Doug Lea
     */
    
    public class InheritableThreadLocal<T> extends ThreadLocal<T> {
        protected T childValue(T parentValue) {// 值传递
            return parentValue;
        }
        ThreadLocalMap getMap(Thread t) {
           return t.inheritableThreadLocals;
        }
        void createMap(Thread t, T firstValue) {
            t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
        }
    }
    

      类InheritableThreadLocal继承自 ThreadLocal,作为子类,它重写了如上所述的三个方法。从上述注释得知,父线程thread-local 变量会被传递至子线程中,下面看看如何传递的。

    
        public Thread() {//创建新线程时调用的构造方法
            this(null, null, "Thread-" + nextThreadNum(), 0);
        }
        public Thread(ThreadGroup group, Runnable target, String name,
                      long stackSize) {
            this(group, target, name, stackSize, null, true);
        }
        /**
         * Initializes a Thread.
         */
        private Thread(ThreadGroup g, Runnable target, String name,
                       long stackSize, AccessControlContext acc,
                       boolean inheritThreadLocals) {
    		 ...
            Thread parent = currentThread();
    		 ...
            setPriority(priority);
            if (inheritThreadLocals && parent.inheritableThreadLocals != null)
                this.inheritableThreadLocals =
                    ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);//①
            /* Stash the specified stack size in case the VM cares */
            this.stackSize = stackSize;
    
            /* Set thread ID */
            this.tid = nextThreadID();
        }
    
    

      跟着进去几层后,发现在①处给inheritableThreadLocals赋值,继续看 ThreadLocal.createInheritedMap的返回值是什么:

        static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
            return new ThreadLocalMap(parentMap);
        }
        /**
         * Construct a new map including all Inheritable ThreadLocals
         * from given parent map. Called only by createInheritedMap.
         *
         * @param parentMap the map associated with parent thread.
         */
        private ThreadLocalMap(ThreadLocalMap parentMap) {
            Entry[] parentTable = parentMap.table; //父线程的table
            int len = parentTable.length;
            setThreshold(len);
            table = new Entry[len];//当前线程的table
            //循环取父线程的值到当前线程
            for (Entry e : parentTable) { 
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                    if (key != null) {
                        Object value = key.childValue(e.value);//值传递
                        Entry c = new Entry(key, value);
                        int h = key.threadLocalHashCode & (len - 1);
                        while (table[h] != null)
                            h = nextIndex(h, len);
                        table[h] = c;
                        size++;
                    }
                }
            }
        }
    
    

      从构造方法ThreadLocalMap可以得知,首先拿到父线程中的键值对表table,然后循环遍历parentTable,把父线程中的这些值通过值传递复制到子线程的ThreadLocalMap对象中,此时子线程的成员变量 inheritableThreadLocals中就有了值。其实,ThreadLocalMap的注释已经明确告诉我们,这里将把父线程的可遗传本地变量(Inheritable ThreadLocals)复制到当前线程中。这种模式,有点类似于ClassLoader的 loadClass() 的机制。
      通过查看Object value = key.childValue(e.value)的实现方式,我们知道这里是值传递。

    小结

      Thread对象通过成员变量ThreadLocal.ThreadLocalMap inheritableThreadLocals维护从父线程(创建该线程的线程)继承而来的数据。原理就是在父线程创建子线程时,如果父线程的inheritableThreadLocals不是null,则ThreadLocalMap中的构造函数会复制一份保存到子线程的inheritableThreadLocals变量中。
      由于本地变量在父子传递过程中通过值传递,所以即使父线程的本地变量发生了改变,子线程的本地变量依旧是创建线程时初始化的值。在实际的应用场景里,基本都是使用线程池来进行多线程编程,因线程池复用线程,而非每次创建新的线程,所以如果更新父线程InheritableThreadLocal 的值,被复用的子线程中的InheritableThreadLocal变量值不会被更新,从而导致数据异常。阿里开源的TransmittableThreadLocal 可以解决此问题,感兴趣的老铁们可以去了解一下。
      以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多点赞支持。

    Reference

  • 相关阅读:
    第六周总结
    石家庄地铁线路查询系统
    第五周总结报告
    二维数组
    第四周总结
    个人作业一(补充)
    第三周总结
    个人作业一
    开课博客
    CentOS7 网卡配置文件解释
  • 原文地址:https://www.cnblogs.com/east7/p/13939336.html
Copyright © 2020-2023  润新知