你是否遇到ThreadLocal父子线程传递值丢失?了解下InheritableThreadLocal?

在了解InheritableThreadLocal先确定你已经了解了ThreadLocal

我们知道ThreadLocal可以实现线程之间值的传递,但是如果是父子线程呢?

我们来看一个简单的例子

		private static final ThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
    private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();

public static void main(String[] args) throws InterruptedException {
        testInheritableThreadLocal();
    }

    public static void testInheritableThreadLocal() {
        inheritableThreadLocal.set("set-inheritableThreadLocal");
        threadLocal.set("set-threadLocal");
        new Thread(() -> {
            String threadLocalCtx = threadLocal.get();
            String inheritableThreadLocalCtx = inheritableThreadLocal.get();
            System.out.println("currentThread:" + Thread.currentThread() + ", get threadLocalCtx:" + threadLocalCtx);
            System.out.println("currentThread:" + Thread.currentThread() + ", get inheritableThreadLocalCtx:" + inheritableThreadLocalCtx);
        }).start();
    }
复制代码

输出结果:

image-20220108174523287

可以看到InheritableThreadLocal是完成了父子线程值的传递,而ThreadLocal 则丢失了值

InheritableThreadLocal 是如何做到的呢?我们来一起简单了解下

类UML图

image-20220108175422513

可以看到InheritableThreadLocal 是继承ThreadLocal

核心源码分析

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 重写了几个方法

  public T get() {
        Thread t = Thread.currentThread();
    		// 该方法被重写,实际调用的是InheritableThreadLocal 的get方法
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }
复制代码

可以看到和ThreadLocal 相比并没有什么特殊的地方

我们可以看看ThreadLocal的get方法

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
复制代码

就是在获取ThreadLocalMap 获取的是一个inheritableThreadLocals 类型和ThreadLocal 一样都是ThreadLocalMap

image-20220108175904008

那么实现父子线程值传递的核心代码在哪里呢?其实是在Thread 类的构造方法里面,我们一起来看看

核心原理

image-20220108180012227

这里是 Thread的构造方法,调用的还是不同参数的构造方法重载,我们直接看具体的实现类

private Thread(ThreadGroup g, Runnable target, String name,
                   long stackSize, AccessControlContext acc,
                   boolean inheritThreadLocals) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

        this.name = name;

        Thread parent = currentThread();
        SecurityManager security = System.getSecurityManager();
        if (g == null) {
            /* Determine if it's an applet or not */

            /* If there is a security manager, ask the security manager
               what to do. */
            if (security != null) {
                g = security.getThreadGroup();
            }

            /* If the security manager doesn't have a strong opinion
               on the matter, use the parent thread group. */
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }

        /* checkAccess regardless of whether or not threadgroup is
           explicitly passed in. */
        g.checkAccess();

        /*
         * Do we have the required permissions?
         */
        if (security != null) {
            if (isCCLOverridden(getClass())) {
                security.checkPermission(
                        SecurityConstants.SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }

        g.addUnstarted();

        this.group = g;
        this.daemon = parent.isDaemon();
        this.priority = parent.getPriority();
        if (security == null || isCCLOverridden(parent.getClass()))
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        this.inheritedAccessControlContext =
                acc != null ? acc : AccessController.getContext();
        this.target = target;
        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();
    }
复制代码

核心代码

if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
复制代码

可以看到在创建子线程的时候将父线程的ThreadLocalMap传递赋值到子线程的ThreadLocalMap

这里需要注意的是一旦子线程被创建以后,再操作父线程中的ThreadLocal变量,那么子线程是不能感知的。因为父线程和子线程还是拥有各自的ThreadLocalMap,只是在创建子线程的时候通过构造方法将父线程的ThreadLocalMap复制给子线程,后续两者就没啥关系了

觉得文章不错欢迎关注公众号:小奏技术

猜你喜欢

转载自juejin.im/post/7051083094308159518