ThreadLocal的增强版—InheritableThreadLocal源码解析

  InheritableThreadLocal相比于ThreadLocal,支持子线程继承父线程的数据,一起来看看它的实现原理吧!

1 从ThreadLocal说起

  在前面ThreadLocal文章中:ThreadLocal源码深度解析与应用案例。我们知道每个线程都具有一个threadLocals属性,用于存放线程本地变量。
  当然如果我们眼睛够细,在我们threadLocals属性下面还可以发现一个同类型的属性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;

  实际上,该属性用于实现线程本地变量的继承性:子线程创建时可以从父线程继承线程本地变量。inheritableThreadLocals主要存储可自动向子线程中传递的ThreadLocalMap,他也对应着一个类:InheritableThreadLocal。
  在hreadLocal的文章的第一个案例的中,同一个ThreadLocal 变量在父线程(main)中被设置值后, 在子线程(child)中是获取不到的。这应该是正常现象,因为在子线程thread 里面调用get 方法时当前线程为thread 线程,而这里调用set方法设置线程变量的是main 线程,两者是不同的线程,自然子线程访问时返回null。
  但是可能有些业务,需要子线程获取到父线程的数据,那么有没有办法让子线程能访问到父线程中的值? 答案是有,那就是InheritableThreadLocal。

2 InheritableThreadLocal的原理

2.1 InheritableThreadLocal的源码

public class InheritableThreadLocal< T >
  extends ThreadLocal< T >

  InheritableThreadLocal继承自ThreadLocal , 保存线程本地变量的集合也是使用的ThreadLocalMap。但是其增加了一个特性,就是让子线程可以访问在父线程中设置的本地变量。
  InheritableThreadLocal类的源码很少,重写了ThreadLocal的3个函数,也是该类的源全部源码:

/**
 * 1
 * InheritableThreadLocal重写的实现,当调用get/set 方法获取当前线程内部的map 变量时,获取
 * 的是inheritableThreadLocals 而不再是threadLocals。
 */
ThreadLocalMap getMap(Thread t) {
    return t.inheritableThreadLocals;
}

/**
 * ThreadLocal的实现
 */
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

/**
 * 操作InheritableThreadLocal对象时,第一次调用set/get方法时
 * 创建的是当前线程的inheritableThreadLocals 变量的实例而不再是threadLocals变量实例。
 */
void createMap(Thread t, T firstValue) {
    t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}

/**
 * ThreadLocal的实现
 */
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

/**
 * 3
 * 该函数在父线程中创建子线程并向子线程复制inheritableThreadLocal变量时使用。
 * 在ThreadLocal中实现为抛出UnsupportedOperationException异常
 */
protected T childValue(T parentValue) {
    return parentValue;
}

/**
 * ThreadLocal的实现
 */
T childValue(T parentValue) {
    throw new UnsupportedOperationException();
}

2.2 传递数据的原理

  传递数据的原理还要从创建线程开始说起!实际上子线程的很多属性如果自己没有指定的话,都会继承父线程的属性,比如线程组、守护/用户线程属性、线程的上下文对象等。

/**
 * Thread类的构造器
 *
 * @param target 线程任务
 * @param name   线程名字
 */
public Thread(Runnable target, String name) {
    //内部调用init方法
    init(null, target, name, 0);
}

/**
 * Thread类的init方法,用于初始化线程的参数
 *
 * @param g         线程组
 * @param target    线程任务
 * @param name      线程名字
 * @param stackSize 当前线程栈大小
 */
private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
    //调用另外一个init方法
    init(g, target, name, stackSize, null, true);
}

/**
 * Thread类的另一个init方法
 *
 * @param g                   线程组
 * @param target              线程任务
 * @param name                线程名字
 * @param stackSize           当前线程栈大小
 * @param acc                 访问控制权限
 * @param inheritThreadLocals 如果为true,则从构造线程(父线程)中继承线程局部变量的初始值
 */
private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc,
                  boolean inheritThreadLocals) {
    //设置名字
    this.name = name;
    //获取当前线程,即父线程(main)
    Thread parent = currentThread();
    //如果inheritThreadLocals为true并且父线程的inheritableThreadLocals 变量不为null
    if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        //4 则将父线程inheritableThreadLocals的数据传递至子线程的inheritableThreadLocals属性中
        //注意这里时传递的引用值,对于引用类型的值(除了string)相当于浅拷贝,实际上他们还是同一个对象
        this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    //…………
}

/**
 * 4
 * ThreadLocal类的方法
 * 父线程的inheritableThreadLocals包装到子线程的inheritableThreadLocals中
 *
 * @param parentMap 父线程的inheritableThreadLocals
 * @return ThreadLocalMap
 */
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
    //5 新建ThreadLocalMap,包含指定的ThreadLocalMap的所有数据
    return new ThreadLocalMap(parentMap);
}

/**
 * ThreadLocalMap类的构造函数,包含指定的ThreadLocalMap的所有数据
 *
 * @param parentMap 指定的ThreadLocalMap对象
 */
private ThreadLocalMap(ThreadLocalMap parentMap) {
    //获取table数组
    ThreadLocalMap.Entry[] parentTable = parentMap.table;
    //获取长度
    int len = parentTable.length;
    //设置扩容阀值
    setThreshold(len);
    //新建table
    table = new ThreadLocalMap.Entry[len];

    for (int j = 0; j < len; j++) {
        ThreadLocalMap.Entry e = parentTable[j];
        if (e != null) {
            @SuppressWarnings("unchecked")
            ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
            if (key != null) {
                //这里终于调用到了重写的childValue方法
                //用于把父线程的inheritableThreadLocals属性的数据复制到新的ThreadLocalMap对象中
                Object value = key.childValue(e.value);
                ThreadLocalMap.Entry c = new ThreadLocalMap.Entry(key, value);
                int h = key.threadLocalHashCode & (len - 1);
                while (table[h] != null)
                    h = nextIndex(h, len);
                table[h] = c;
                size++;
            }
        }
    }
}

3 总结和案例

  InheritableThreadLocal 类重写了三个方法。createMap将创建的ThreadLocalMap赋值给具体线程的inheritableThreadLocals变量。getMap将返回具体线程的inheritableThreadLocals变量,从而让本地变量保存到了inheritableThreadLocals变量里面,那么线程在通过InheritableThreadLocal 类实例的set 或者get 方法设置变量时,就会创建当前线程的inheritableThreadLocals 变量。
  当在父线程中创建子线程时,构造函数会把父线程中inheritableThreadLocals变量里面的本地变量复制一份(浅克隆)保存到子线程的inheritableThreadLocals 变量里面。
  InheritableThreadLocal传递数据的案例:

public class InheritableThreadLocalTest {
    static InheritableThreadLocal<InheritableThreadLocalTest> threadLocal = new InheritableThreadLocal<>();

    public static void set() {
        threadLocal.set(new InheritableThreadLocalTest());
    }

    public static InheritableThreadLocalTest get() {
        return threadLocal.get();
    }

    public static void main(String[] args) throws InterruptedException {
        System.out.println("主线程中尝试获取值====>" + get());
        System.out.println("主线程中设置值====>");
        set();
        //主线程中尝试获取值
        System.out.println("主线程中再次尝试获取值====>" + get());
        System.out.println();

        System.out.println("开启一条子线程====>");
        Thread thread = new Thread(new Th1(), "child");
        thread.start();
        //主线程等待子线程执行完毕
        thread.join();
        System.out.println();

        System.out.println("等待子线程执行完毕,主线程中再次尝试获取值====>" + get());
        System.out.println("获取到的还是父线程原来的值,即父可以传递数据给子,子不能传递数据给父");
    }

    static class Th1 implements Runnable {
        @Override
        public void run() {
            System.out.println("子线程中尝试获取值====>" + get());
            System.out.println("能够直接获取到,说明父线程的数据传递给了子线程,并且获取到的对象和父类获取到的对象还是同一个,即浅拷贝");
            System.out.println("子线程中设置值====>");
            set();
            System.out.println("子线程中再次尝试获取值====>" + get());
            System.out.println("此时获取到的,是自己设置的值");
            System.out.println();

        }
    }

    @Override
    public String toString() {
        return this.hashCode() + "";
    }

}

  结果:

主线程中尝试获取值====>null
主线程中设置值====>
主线程中再次尝试获取值====>1878246837

开启一条子线程====>
子线程中尝试获取值====>1878246837
能够直接获取到,说明父线程的数据传递给了子线程,并且获取到的对象和父类获取到的对象还是同一个,即浅拷贝
子线程中设置值====>
子线程中再次尝试获取值====>185894025
此时获取到的,是自己设置的值


等待子线程执行完毕,主线程中再次尝试获取值====>1878246837
获取到的还是父线程原来的值,即父可以传递数据给子,子不能传递数据给父

  可以看出,子线程可以正常获取到父线程本地变量的值。InheritableThreadLocal常常用在调用链路追踪上。

如果有什么不懂或者需要交流,可以留言。另外希望点赞、收藏、关注,我将不间断更新各种Java学习博客!

猜你喜欢

转载自blog.csdn.net/weixin_43767015/article/details/106943401