InheritableThreadLocal是如何实现变量线程共享的

412 阅读3分钟

前言

先说ThreadLocal吧,最早使用的场景是将用户信息存储在ThreadLocal里面,如果是同一个线程进来就不用重新获取用户信息了,直接从本地线程变量里面获取,在业务的很多地方都需要使用用户信息,存储到ThreadLocal里方便调用,每个线程持有一个ThreadLocalMap对象,Map的key是Threadlocal实例本身,value是threadLocals真正存储的值;

ThreadLocal

ThreadLocal主要作用

  1. 线程间的数据隔离
  2. 存储事务信息
  3. Session会话管理
private  final static ThreadLocal<String> threadLocal =new ThreadLocal<>();

public static void main(String[] args) {
    threadLocal.set("parent thread data");
    System.out.println(Thread.currentThread().getName()+"ThreadLocal data:" +threadLocal.get());
    new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+" data:" +threadLocal.get());
        }
    }).start();

}

子线程无法获取主线程ThreadLocal存的变量

C05871B3-BEBC-4F9B-BFD6-7C5474E8D3D2.png

InheritableThreadLocal

如果我们把ThreadLocal换成InheritableThreadLocal

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

public static void main(String[] args) {
    threadLocal.set("parent thread data");
    System.out.println(Thread.currentThread().getName()+"ThreadLocal data:" +threadLocal.get());
    new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+" data:" +threadLocal.get());
        }
    }).start();

}

可见子线程就可以访问主线程的变量了

04F0F415-D074-4AB6-98B8-8E0B23560662.png

InheritableThreadLocal重写方法

InheritableThreadLocal继承了ThreadLocal方法,并且重写了getMap和 createMap方法


public class InheritableThreadLocal<T> extends ThreadLocal<T> {

    //该方法主要是父线程向子线程复制InheritableThreadLocal变量
    protected T childValue(T parentValue) {
        return parentValue;
    }

    //重写了ThreadLocal类,现在getMap指向的引用不再是theadlocals而是inheritableThreadLocals
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }

    //创建ThreadLocalMap,指向inheritableThreadLocals
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }

主线程向子线程传递数据

要从Thread类看起,Thread里面有两个变量

49ED95D9-BEDD-4B21-A9B2-F4F958C0B6C6.png Thread的init初始化方法

private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize) {
    init(g, target, name, stackSize, null, true);
}

关键代码,inheritThreadLocals和parent的inheritThreadLocals不为空,这里的parent指的就是主线程,将父线程传递给子线程

2EA2EE2D-C395-4337-95FD-5673D6F00BA9.png

createInheritedMap方法

接着来看 createInheritedMap方法,返回了一个ThreadLocalMap;

static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
    return new ThreadLocalMap(parentMap);
}

ThreadLocalMap主要是parentMap的变量逐一复制到子线程

        private ThreadLocalMap(ThreadLocalMap parentMap) {
        //父线程ThreadLocalMap,获取ThreadLocal存在方式为entry数组
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            setThreshold(len);
            table = new Entry[len];

            for (int j = 0; j < len; j++) {
                Entry e = parentTable[j];
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                    if (key != null) {
                      //这里调用了childValue方法,父线程向子线程复制ThreadLocal变量的方法
                        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++;
                    }
                }
            }
        }

InheritableThreadLocal支持线程池之间的传递么?

通过线程之前的变量传递,可以通过InheritableThreadLocal解决,那么开启线程池,在线程池中进行变量的传递共享可以么?

使用单例的线程池,模拟父子线程之前的传递;

ExecutorService executorService = Executors.newSingleThreadExecutor();
InheritableThreadLocal<String> threadLocal =new InheritableThreadLocal<>();
for (int i = 0; i <4 ; i++) {
    threadLocal.set("parent thread data -"+i);
    Thread.sleep(3000);
    CompletableFuture.runAsync(() -> System.out.println(threadLocal.get()), executorService);

}
executorService.shutdownNow();

执行结果,可以看出是如何完成变量共享的,那么如何才能在线程池内完成线程共享呢,下一篇会讲到;

532B1D41-C72F-4BD9-9F00-8CDD0CF40104.png

总结

至此就可以得出结论,使用InheritableThreadLocal存储变量,有一步的操作就是将父线程里存储的变量,通过entry数组的方式循环进行传递,这样就实现了线程之前数据的可见性。