美文网首页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