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

posted @ 2020-11-06 22:37  楼兰胡杨  阅读(1180)  评论(0编辑  收藏  举报