美文网首页JavaConcurrent
InheritableThreadLocal—可继承的Threa

InheritableThreadLocal—可继承的Threa

作者: 星空怎样 | 来源:发表于2020-05-28 17:50 被阅读0次

    [toc]

    前言

    介绍InheritableThreadLocal之前,先列举有关Thread的关键点:

    • 每一个Thread线程都有属于自己的ThreadLocalMap,里面的节点属于弱引用的Entry,如下:
    static class Entry extends WeakReference<ThreadLocal<?>> {
                /** The value associated with this ThreadLocal. */
                Object value;
    
                Entry(ThreadLocal<?> k, Object v) {
                    super(k);
                    value = v;
                }
            }
    
    • 从ThreadLocal中get值的时候,首先通过Thread.currentThread得到当前线程,然后拿到这个线程的ThreadLocalMap,通过当前ThreadLocal对象取得Entry中的value。
    • set值的时候同理,更改当前线程的ThreadLocalMap中的Entry为当前ThreadLocal对象的value值。

    ThreadLocal的BUG

    如果子线程想要拿到父线程的ThreadLocal的值怎么办,比如有以下代码,由于ThreadLocal的实现机制,在子线程get时,我们拿到是当前Thread对象的子线程的对象,那么他的ThreadLocalMap为null,那么我们得到的value也是null。

    final ThreadLocal threadLocal=new ThreadLocal(){
        //给threadLocal设置一个初始值
        @Override
        protected Object initialValue(){
            return "ceshi";
        }
    };
    new Thread(new Runnable(){
        @Override
        public void run(){
            threadLocal.get();//null
        }
    }).start();
    

    所以子线程是拿不到父线程的ThreadLocalMap的值,现在就需要InheeritableThreadLocal了。

    InheritableThreadLocal的实现

    很多时候我们有子线程获取父线程的ThreadLocal的需求,要如何解决,这就是InheritableThreadLocal所做的事情,先看他的实现:

    public class InheritableThreadLocal<T> extends ThreadLocal<T>{
        /**
        * 计算此可继承线程本地的子线程的初始值,
        * 变量作为父级值,传入方法,进行处理
        * 默认返回父线程的值,如果需要处理,
        * 重写该方法
        */
        protected T childValue(T parentValue){
            return parentValue;
        }
        /**
        * 重写ThreadLocal类中的getMap方法,在原来ThreadLocal中返回t.threadLocals,
        * 而现在返回inheritableThreadLocals,因为Thread类中也有一个要保存父子传递的变量
        */
        ThreadLocalMap getMap(Thread t){
            return t.inheritableThreadLocals;
        }
        
        /**
        * 在创建ThreadLocalMap的时候不给t.threadLocal赋值,
        * 而是给inheritableThreadLocals变量赋值
        */
        void createMap(Thread t,T firstValue){
            t.inheritableThreadLocals=new ThreadLocalMap(this,firstValue);
        }
    }
    

    如果使用InheritableThreadLocal,那么保存所有的东西已经不是在原来的t.threadLocals里面,而是一个新的t.inheritableThreadLocals变量中,下面是Thread类中的两个变量的定义。

    //与此线程相关的ThreadLocal值。这map是由ThreadLocal类维护。
    ThreadLocal.ThreadLocalMap threadLocals=null;
    //与此线程相关的可继承线程本地值。这map是由InheritableThreadLocal类维护。
    ThreadLocal.ThreadLocalMap inheritableThreadLocals=null;
    

    InheritableThreadLocal如何实现拿到父线程的值

    使用一个常见的想法就是把父线程的所有值都copy到子线程中。

    下面看一下new Thread的部分代码

    //Thread init的部分源码
    /**
    * 初始化线程
    * @param g 线程组
    * @param target 调用run()方法的对象
    * @param name the 新线程的名称
    * @param stackSize 新线程的所需堆栈大小,或 0表示将忽略此参数。
    * @param acc 要继承的AccessControlContext;如果为null,则为 AccessController.getContext()
    * @param inheritThreadLocals 如果{@code true},则从构造线程继承可继承线程局部变量的初始值
    * inheritThreadLocals这个参数一般都是true,只有系统调用传参时候才有可能是false
    */
    private void init(ThreadGroup g,Runnable target,String name,
                        long stackSize,AccessControlContext acc,
                        boolean inheritThreadLocals){
        //省略上面代码                    
        if (inheritThreadLocals && parent.inheritableThreadLocals != null){
            //copy父线程的map,创建一个新的map赋值给当前线程的inherableThreadLocals
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        }
        //省略下面代码
    }
    

    而拷贝属于浅拷贝,key和value都是原来的引用地址

    //ThreadLocal的部分源码
    /**
    * 创建继承的线程局部变量映射的工厂方法。
    * 设计为仅从Thread构造函数调用
    * @param  parentMap 与父线程关联的map
    * @return 包含父级可继承绑定的映射
    */
    static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
    }
    /**
    * 构造一个新的map,其中包括给定父map中的所有InheritableThreadLocals
    * 仅由createInheritedMap调用。
    * @param parentMap 与父线程关联的map
    */
    private ThreadLocalMap(ThreadLocalMap parentMap) {
        //获取父map的Entry数组
        Entry[] parentTable = parentMap.table;
        //获取数组长度
        int len = parentTable.length;
        //设置调整大小阈值以保持最坏的2/3负载系数。大于这个数会进行扩容
        setThreshold(len);
        //创建一个新的Entry数组,table.length必须始终为2的幂。
        table = new Entry[len];
        for (int j = 0; j < len; j++) {
            //获取父数组的元素
            Entry e = parentTable[j];
            if (e != null) {
                @SuppressWarnings("unchecked")
                //Entry的获取key
                ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                if (key != null) {
                    //获取value,调用childValue可以对父级value进行处理,
                    //默认直接返回value,需要额外处理,就重写
                    Object value = key.childValue(e.value);
                    //创建一个新的Entry,key和value还是使用原来的引用地址
                    Entry c = new Entry(key, value);
                    //计算hash
                    int h = key.threadLocalHashCode & (len - 1);
                    //对应hash上有元素,就一直循环知道找到非空的位置(解决冲突的方法就是线性探查法)
                    while (table[h] != null){
                        //下一个索引位置
                        h = nextIndex(h, len);
                    }
                    //赋值    
                    table[h] = c;
                    //大小加1
                    size++;
                }
            }
        }
        /**
         * 增量和模数len。
         */
        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }
    }
    

    总结

    InheritableThreadLocal为什么能解决父子线程传递ThreadLocal值的问题:

    • 在创建InheritableThreadLocal对象的时候赋值非线程的t.inheritableThreadLocals变量
    • 在创建新的线程时候会检查父线程中t.inheritableThreadLocals变量是否为null,如果不为null,则copy一份ThreadLocalMap到子线程的t.inheritableThreadLocals成员变量中
    • 因为InheritableThreadLocal重写了getMap(Thread t)和createMap(Thread t, T firstValue)方法,所以get值的时候,就会从t.inheritableThreadLocals中拿到map对象,从而实现了可以拿到父线程ThreadLocal中的值

    相关文章

      网友评论

        本文标题:InheritableThreadLocal—可继承的Threa

        本文链接:https://www.haomeiwen.com/subject/nawaahtx.html