InheritableThreadLocal传递父线程的线程本地变量给子线程

1 概念

Java多线程编程中,通过ThreadLocal实现了线程本地变量机制,通过空间换时间达到了线程隔离的目的。

但是,现在有这样一个需求:父线程中的线程本地变量需要传递给子线程使用。

针对这种情况,通过ThreadLocal是无法完成的,因为ThreadLocal中保存的是线程的本地变量,父线程的线程本地变量只有自己能拿到,子线程无法拿到。如下代码演示:

package com.tao.springbootdemo.thread;

public class ParentAndChildThreadMain {

    public static void main(String[] args) {

        // 父线程
        Thread parentThread = new Thread(new Runnable() {

            // 父线程中的线程局部变量
            private ThreadLocal<String> threadLocal = new ThreadLocal<>();
            // private InheritableThreadLocal<String> threadLocal = new InheritableThreadLocal<>();

            @Override
            public void run() {
                System.out.println("> 父线程中设置线程的本地变量");
                threadLocal.set("local variable");
                System.out.println("> 父线程中拿到的线程本地变量是:" + threadLocal.get());

                // 在父线程中再起一个子线程
                Thread childThread = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("> 子线程中获取父线程的本地变量");
                        System.out.println("> 子线程中拿到的父线程本地变量是:" + threadLocal.get());
                    }
                });
                childThread.setName("子线程!");
                childThread.start();
            }
        });
        parentThread.setName("父线程!");
        parentThread.start();
    }
}

得到的输出结果是:

> 父线程中设置线程的本地变量
> 父线程中拿到的线程本地变量是:local variable
> 子线程中获取父线程的本地变量
> 子线程中拿到的父线程本地变量是:null

Process finished with exit code 0

那么,怎样解决这个需求呢?

Java提供了一个InheritableThreadLocal来解决线程本地变量在父子线程中传递的问题!!!

如下代码演示:

package com.tao.springbootdemo.thread;

public class ParentAndChildThreadMain {

    public static void main(String[] args) {

        // 父线程
        Thread parentThread = new Thread(new Runnable() {

            // 父线程中的线程局部变量
            // private ThreadLocal<String> threadLocal = new ThreadLocal<>();
            private InheritableThreadLocal<String> threadLocal = new InheritableThreadLocal<>();

            @Override
            public void run() {
                System.out.println("> 父线程中设置线程的本地变量");
                threadLocal.set("local variable");
                System.out.println("> 父线程中拿到的线程本地变量是:" + threadLocal.get());

                // 在父线程中再起一个子线程
                Thread childThread = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("> 子线程中获取父线程的本地变量");
                        System.out.println("> 子线程中拿到的父线程本地变量是:" + threadLocal.get());
                    }
                });
                childThread.setName("子线程!");
                childThread.start();
            }
        });
        parentThread.setName("父线程!");
        parentThread.start();

    }
}

得到的输出结果为:

> 父线程中设置线程的本地变量
> 父线程中拿到的线程本地变量是:local variable
> 子线程中获取父线程的本地变量
> 子线程中拿到的父线程本地变量是:local variable

Process finished with exit code 0

我们只是将ThreadLocal改成了InheritableThreadLocal,子线程中便能取到父线程的线程局部变量了。

2 原理分析

2.1 类源码分析

首先看下Thread类:

public class Thread implements Runnable {
    ......

    /**
     * 与此线程相关的ThreadLocal值,保存线程本地变量。
     * 这个map由ThreadLocal类维护。
     */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /*
     * 与此线程相关的那些从父线程继承而来的ThreadLocal值。
     * 这个map由InheritableThreadLocal类维护。
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

可以看出,threadLocalsinheritableThreadLocals由两个实例域维护,相互之间互不影响

然后,再看下InheritableThreadLocal的源码:

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    /**
     * 该函数在父线程创建子线程,向子线程复制InheritableThreadLocal变量时使用
     */
    protected T childValue(T parentValue) {
        return parentValue;
    }

    /**
     * 由于重写了getMap,操作InheritableThreadLocal时,
     * 将只影响Thread类中的inheritableThreadLocals变量,
     * 与threadLocals变量不再有关系。
     */
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }

    /**
     * 类似于getMap,操作InheritableThreadLocal时,
     * 将只影响Thread类中的inheritableThreadLocals变量,
     * 与threadLocals变量不再有关系。
     */
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}
  • InheritableThreadLocal继承自ThreadLocal
  • 重写了ThreadLocal的3个函数;
  • 重写的getMap()createMap()函数非常重要,操作的是t.inheritableThreadLocals,这两个函数在调用set(T value)get()函数的时候起到了非常重要的作用;
  • ThreadLocal互不影响。

2.2 传值分析

首先,从父线程创建子线程开始。

父线程调用new Thread()创建子线程:

Thread childThread = new Thread();

调用了Thread类的构造函数:

    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }

    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        // 默认情况下,设置inheritThreadLocals为可传递
        init(g, target, name, stackSize, null, true);
    }

    /**
     * 初始化一个线程。
     * 此函数有两处调用:
     * 1、上面的 init(),不传AccessControlContext,inheritThreadLocals = true;
     * 2、传递AccessControlContext,inheritThreadLocals = false
     */
    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        
        ......(其他代码)
        
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        
        ......(其他代码)
    }
  • 可以看到,采用默认方式产生子线程时,inheritThreadLocals = true

  • 若此时父线程inheritableThreadLocals不为空,则将父线程的inheritableThreadLocals传递给子线程。如果父线程中使用的是InheritableThreadLocal保存线程本地变量,那么在调用set(T value)的时候,操作的就是线程的inheritableThreadLocals,所以parent.inheritableThreadLocals != null

  • 其中,通过调用ThreadLocal.createInheritedMap(parent.inheritableThreadLocals)来将父线程的inheritableThreadLocals传入并创建子线程的inheritableThreadLocals

查看ThreadLocal.createInheritedMap函数的源码:

    // 创建线程的inheritableThreadLocals
    static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
    }

    /**
     * 接收传进来的 parent.inheritableThreadLocals,
     * 构建一个包含parent.inheritableThreadLocals中所有键值对的ThreadLocalMap,
     * 该函数只被 createInheritedMap() 调用.
     */
    private ThreadLocalMap(ThreadLocalMap parentMap) {
        Entry[] parentTable = parentMap.table;
        int len = parentTable.length;
        setThreshold(len);
        table = new Entry[len];

        // 逐个复制parent.inheritableThreadLocals中的Entry
        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) {
                    Object value = key.childValue(e.value);
                    // 创建子线程中对应的Entry
                    Entry c = new Entry(key, value);
                    int h = key.threadLocalHashCode & (len - 1);
                    while (table[h] != null)
                        h = nextIndex(h, len);
                    // 保存
                    table[h] = c;
                    size++;
                }
            }
        }
    }

从上边的源码分析可以看出,在创建子线程的时候,将父线程中inheritableThreadLocals对应的ThreadLocalMap中的所有的Entry全部复制到一个新的ThreadLocalMap中,最后将这个ThreadLocalMap赋值给了子线程的inheritableThreadLocals

当在子线程中调用父线程的InheritableThreadLocal<String> threadLocalthreadLocal.get()方法,获取父线程的线程本地变量的时候,查看源码:

    public T get() {
        Thread t = Thread.currentThread();
        // InheritableThreadLocal重写了getMap,返回的是线程的inheritableThreadLocals
        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();
    }

可以看出,

  • 因为在InheritableThreadLocal类中重写了getMap(),返回的是线程的inheritableThreadLocals
  • 所以,在子线程中调用get()拿到的是当前线程中inheritableThreadLocals这个ThreadLocalMap
  • 最后get()返回的值就是保存在子线程的inheritableThreadLocals中的值。

2.3 子线程会随时更新从父线程传递来的线程本地变量吗?

编写代码测试:

package com.tao.springbootdemo.thread;

public class ParentAndChildThreadMain {

    public static void main(String[] args) {

        // 父线程
        Thread parentThread = new Thread(new Runnable() {

            // 父线程中的线程局部变量
            // private ThreadLocal<String> threadLocal = new ThreadLocal<>();
            private InheritableThreadLocal<String> threadLocal = new InheritableThreadLocal<>();

            @Override
            public void run() {
                System.out.println("> 父线程中设置线程的本地变量");
                threadLocal.set("local variable");
                System.out.println("> 父线程中拿到的线程本地变量是:" + threadLocal.get());

                // 在父线程中再起一个子线程
                Thread childThread = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("> 子线程中获取父线程的本地变量");
                        System.out.println("> 子线程中拿到的父线程本地变量是:" + threadLocal.get());
                        try {
                            System.out.println("> 子线程 sleep 5秒");
                            Thread.sleep(5000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("> 子线程中再次获取父线程的本地变量");
                        System.out.println("> 子线程中拿到的父线程本地变量是:" + threadLocal.get());

                    }
                });
                childThread.setName("子线程!");
                childThread.start();

                try {
                    System.out.println("> 父线程 sleep 2秒");
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("> 父线程中再次设置线程的本地变量");
                threadLocal.set("AAA");
                System.out.println("> 父线程中拿到的改变后的线程本地变量是:" + threadLocal.get());
            }
        });
        parentThread.setName("父线程!");
        parentThread.start();
    }
}

程序输出结果:

> 父线程中设置线程的本地变量
> 父线程中拿到的线程本地变量是:local variable
> 父线程 sleep 2秒
> 子线程中获取父线程的本地变量
> 子线程中拿到的父线程本地变量是:local variable
> 子线程 sleep 5秒
> 父线程中再次设置线程的本地变量
> 父线程中拿到的改变后的线程本地变量是:AAA
> 子线程中再次获取父线程的本地变量
> 子线程中拿到的父线程本地变量是:local variable

Process finished with exit code 0

可以看到,子线程不会更新从父线程传递过来的线程本地变量!!!

3 总结

InheritableThreadLocal主要用于在创建子线程的时候,需要自动继承父线程的线程本地变量以便使用。

发布了178 篇原创文章 · 获赞 152 · 访问量 61万+

猜你喜欢

转载自blog.csdn.net/hbtj_1216/article/details/100511851