InheritableThreadLocal详解

目录

前言

正文

1.类ThreadLocal不能实现值继承 

2.使用 InheritableThreadLocal 体现值继承特性

3.值继承特性在源代码中的执行流程

4.父线程有新的值,子线程还是旧值:不可变类型 

5.子线程具有最新的值,父线程还是旧值 

6.子线程可以感应对象属性值的变化:可变类型 

7.重写 childValue 方法实现对继承值的加工 


前言

使用类InheritableThreadLocal 可以再子线程中取得父线程继承下来的值,而ThreadLocal并不具备,所以让我们继续了解。


正文

1.类ThreadLocal不能实现值继承 

新建测试用例:

public class ThreadLocalNoExtends {
    static class Tools{
        public static ThreadLocal t1 = new ThreadLocal();
    }
    static class ThreadA extends Thread{
        @Override
        public void run() {
            try{
                for (int i = 0; i < 10; i++) {
                    System.out.println("在ThreadA线程中取值="+Tools.t1.get());
                    Thread.sleep(100);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        try {
            for (int i = 0; i < 10; i++) {
                if (Tools.t1.get() == null){
                    Tools.t1.set("此值是main线程放入的!");
                }
                System.out.println("    在Main线程中取值="+Tools.t1.get());
                Thread.sleep(100);
            }
            Thread.sleep(5000);
            ThreadA a = new ThreadA();
            a.start();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果如图:

因为 man 线程创建了 ThreadA 线程,所以main线程是ThreadA 线程的父线程。从运行结果中可以发现,由于ThreadA线程并没有继承main 线程,所以ThreadLocacl并不具有值继承特性,这时就要使用 InheritableThreadLocal 类进行替换了。 

2.使用 InheritableThreadLocal 体现值继承特性

使用 InheritableThreadLocal 类可以让子线程从父线程中继承值。

代码:

public class InheritableThreadLocal {
    static class Tools{
        public static java.lang.InheritableThreadLocal t1 = new java.lang.InheritableThreadLocal();
    }
    static class ThreadA extends Thread{
        @Override
        public void run() {
            try{
                for (int i = 0; i < 10; i++) {
                    System.out.println("在ThreadA线程中取值="+Tools.t1.get());
                    Thread.sleep(100);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        try{
            for (int i = 0; i < 10; i++) {
                if (Tools.t1.get() == null){
                    Tools.t1.set("此值时main线程放入的!");
                }
                System.out.println("    在Main线程中取值="+Tools.t1.get());
                Thread.sleep(100);
            }
            ThreadA a = new ThreadA();
            a.start();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果:

ThreadA子线程获取的值时从父线程main继承的。

3.值继承特性在源代码中的执行流程

使用 InheritableThreadLocal 的确可以实现值继承的特性,那么在JDK源代码中式如何实现这个特性的呢?进行分析。

1)首先看一下InheritableThreadLocal类的源代码,如下:

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    /**
     * Computes the child's initial value for this inheritable thread-local
     * variable as a function of the parent's value at the time the child
     * thread is created.  This method is called from within the parent
     * thread before the child is started.
     * <p>
     * This method merely returns its input argument, and should be overridden
     * if a different behavior is desired.
     *
     * @param parentValue the parent thread's value
     * @return the child thread's initial value
     */
    protected T childValue(T parentValue) {
        return parentValue;
    }

    /**
     * Get the map associated with a ThreadLocal.
     *
     * @param t the current thread
     */
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }

    /**
     * Create the map associated with a ThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the table.
     */
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

在 InheritableThreadLocal类的源代码中存在3个方法,这3个方法都是对父类ThreadLocal中的同名方法进行重写后得到的,因为在源代码中并没有使用@Override进行标识,所以在初期分析时如果不注意,流程是比较绕的。

        InheritableThreadLocal类中的这3个核心方法都是对ThreadLocal类中的方法进行重写后得到的,ThreadLocal类中这三个方法的源代码如下:

    /**
     * Get the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param  t the current thread
     * @return the map
     */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

    /**
     * Create the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the map
     */
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

    /**
     * Method childValue is visibly defined in subclass
     * InheritableThreadLocal, but is internally defined here for the
     * sake of providing createInheritedMap factory method without
     * needing to subclass the map class in InheritableThreadLocal.
     * This technique is preferable to the alternative of embedding
     * instanceof tests in methods.
     */
    T childValue(T parentValue) {
        throw new UnsupportedOperationException();
    }

从源代码中可以看出,ThreadLocal类操作的是threadLocals实例变量,而InheritableThreadLocal类操作的是inheritableThreadLocals实例变量,这是两个变量。

2)回头继续看main()方法中使用 main 线程执行InheritableThreadLocal.set()方法,源代码如下:

 调用InheritableThreadLocal对象中的set()方法其实就是调用ThreadLocal类中的set()方法,因为InheritableThreadLocal并没有重写set()方法。

3)下面分析一下 ThreadLocal 类中的 set() 方法,源代码如下:

    /**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            map.set(this, value);
        } else {
            createMap(t, value);
        }
    }

在执行ThreadLocal类中的set()方法时,有两个方法已经被 InheritableThreadLocal 类重写了,分别是 getMap(t) 和 createMap(t,value) 一定要留意,在执行这两个方法时,调用的时InheritableThreadLocal 类中重写的 getMap(t) 方法和  createMap(t,value) 方法。再次查看一下重写这2个方法在 InheritableThreadLocal 类中的源代码。

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
  
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }

  
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

4)通过查看 InheritableThreadLocal 类中 getMap(Thread t) 方法和 createMap(Thread t,T firstValue) 方法的源代码可以明确一个重要的知识点,那就是不再向 Thread 类中的 ThreadLocalMap threadLocals 存入数据了,而是向 ThreadLocal.ThreadLocalMap inheritableThread 存入数据,这 2 个对象在 Thread 类中的声明如下。

上面的分析明确了一个知识点,就是main线程向 inheritableThreadLocal对象存入数据,对象inheritableThreadLocals 就是存数据的容器,那么子线程如何继承父线程中的 inheritableThreadLocals 对象的值呢?

5)这个实现的思路就是在创建子线程 ThreadA 时,子线程主动引用父线程 main 里面的 inheritableThreadLocals对象值,源代码如下:

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);
      ````
    }

        因为init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc,boolean inheritThreadLocals)方法是被Thread的构造方法调用的,所以在new ThreadA() 中,在 Thread.java 源代码内部会自动调用 init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc,boolean inheritThreadLocals) 方法。

        在 init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc,boolean inheritThreadLocals) 方法的最后一个参数代表当前是否会从父线程中继承值。因为这个值被永远传入 true ,所以每一次都会继承值。传入 true 的源代码在 private void init(ThreadGroup g, Runnable target, String name, long stackSize) 方法中,源代码如下:

    /**
     * Initializes a Thread with the current AccessControlContext.
     * @see #init(ThreadGroup,Runnable,String,long,AccessControlContext,boolean)
     */
    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null, true);
    }

        这一过程也就是方法  private void init(ThreadGroup g, Runnable target, String name, long stackSize)。

        调用方法 init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc,boolean inheritThreadLocals)并对最后一个参数永远传入 true,即最后一个参数 inheritThreadLocals 永远为 true。

6)执行 init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc,boolean inheritThreadLocals)方法中的 if 语句,如下所示。

 if (inheritThreadLocals && parent.inheritableThreadLocals != null)

        如果运算符 && 左边的表达式 inheritThreadLocal 的值为 true,就要开始运算 && 右边的表达式   parent.inheritableThreadLocals != null 了。

        当向 main 线程中的 inheritableThreadLocals 存放数据时,因为对象 inheritableThreadLocals并不是空的,所以 && 运算符两边都为 true。那么程序继续运行,对当前线程的 inheritableThreadLocals 对象变量进行赋值,代码如下。

this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

7)代码 this.inheritableThreadLocals 中的 this 就是当前 Thread.java 类的对象,执行 create-InheritedMap() 方法的目的是先创建一个新的 ThreadLocalMap 对象,然后在将 ThreadLocalMap   对象赋值给 ThreadA 对象中的 inheritableThreadLocals 变量, createInheritedMap()方法的源代码如下。

    /**
     * Factory method to create map of inherited thread locals.
     * Designed to be called only from Thread constructor.
     *
     * @param  parentMap the map associated with parent thread
     * @return a map containing the parent's inheritable bindings
     */
    static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
    }

8)下面继续分析一下 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;
            int len = parentTable.length;
            setThreshold(len);
            table = new Entry[len];        //新建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);    //为了让程序员可以自己覆盖,所以没有写成Object value = e.value。
                        Entry c = new Entry(key, value);    //实例化新的 Entry 对象
                        int h = key.threadLocalHashCode & (len - 1);
                        while (table[h] != null)
                            h = nextIndex(h, len);
                        table[h] = c;                       //将父线程中的数据复制到新数组中
                        size++;    
                    }
                }
            }
        }

        在 ThreadMap 类构造方法的源代码中,子线程创建了全新的 table = new Entry[len];对象来存储数据,数据源自父线程。

        最为关键的代码如下(这里调用这个函数给出e.value会返回e.value,没有直接写成Object value = e.value 而是使用了函数,就是给了程序员能够重写的机会。向下继续了解就知道为什么会这么做了):

 Object value = key.childValue(e.value);

        由于 value 数据类型可以是不可变的,也可以是可变的,因此会出现两种截然不同的结果。

        先看不可变数据类型的测试:

public class Test {
    public static void main(String[] args) {
        String a = "abc";
        String b = a ;
        System.out.println(a+" "+b);
        a = "xyz";
        System.out.println(a+" "+b);
    }
}

运行结果:

String数据类型是不可变的,对String赋新的值常量值会开辟新的内存空间。

再来看一看可变数据类型的测试。

public class Test {
    static class Userinfo{
        public String username;

        public Userinfo(String username) {
            super();
            this.username = username;
        }
    }
    public static void main(String[] args) {
        Userinfo userinfo1 = new Userinfo("我是旧值");
        Userinfo userinfo2 = userinfo1;
        System.out.println(userinfo1.username+" "+userinfo2.username);
        userinfo1.username = "我是新值";
        System.out.println(userinfo1.username+" "+userinfo2.username);
    }
}

运行结果:

自定义 Userinfo  数据类型的内容是可变的,对象 userinfo1 和 userinfo2 引用同一个地址的 Userinfo 类的对象,一个对象的属性改了,另一个也能感应到。

        如果 e.value 中的 value 是不可变数据类型,那么主线程使用 InheritableThreadLocal 类执行 set(String) 操作。当子线程对象创建并启动时,子线程中的数据就是主线程旧的数据。由于String数据类型是不可变的,因此主线程和子线程拥有各自的 String 存储空间,只是空间中的值是一样的。当主线程使用新的 String 数据时,只是更改了主线程 String 空间中的值,子线程还是使用旧的 String  数据类型是不可变的。

        如果 e.value 是可变数据类型,那么主线程使用 InheritableThreadLocal 类执行 set(Userinfo)操作。当子线程对象创建完毕并启动时,主线程和子线程拥有的 Userinfo 对象是同一个。主线程改变 Userinfo 中的属性值,这时子线程可以立即取得最新的属性值。只要main主线程更改了 Userinfo 中的属性值,子线程就能感应到。

4.父线程有新的值,子线程还是旧值:不可变类型 

新的测试用例 

public class InheritableThreadLocal {
    static class Tools {
        public static java.lang.InheritableThreadLocal t1 = new java.lang.InheritableThreadLocal();
    }

    static class ThreadA extends Thread {
        @Override
        public void run() {
            try {
                for (int i = 0; i < 10; i++) {
                    System.out.println("在ThreadA线程中取值=" + Tools.t1.get());
                    Thread.sleep(1000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        if (Tools.t1.get() == null) {
            Tools.t1.set("此值时main线程放入的!");//在此处执行。
        }
        System.out.println("    在Main线程中取值=" + Tools.t1.get());
        Thread.sleep(100);
        ThreadA a = new ThreadA();
        a.start();
        Thread.sleep(5000);
        Tools.t1.set("此值是main线程 newnewnewnew 放入的");
    }
}

程序运行后,子线程还是持有旧的数据,打印结果如下:

5.子线程具有最新的值,父线程还是旧值 

测试代码:


import java.lang.InheritableThreadLocal;

public class InheritableThreadLocal102 {
    static class Tools {
        static InheritableThreadLocal t1 = new InheritableThreadLocal();
    }

    static class ThreadA extends Thread {
        @Override
        public void run() {
            try {
                for (int i = 0; i < 10; i++) {
                    System.out.println("在ThreadA线程中取值=" + Tools.t1.get());
                    Thread.sleep(1000);
                    if (i == 5) {
                        Tools.t1.set("我是ThreadA的 newnewnew 最新的值");
                        System.out.println("ThreadA 已经存在最新的值--------------------");
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        if (Tools.t1.get() == null) {
            Tools.t1.set("此值是main线程放入的");
        }
        System.out.println("    在Main线程中取值=" + Tools.t1.get());
        Thread.sleep(100);
        ThreadA a = new ThreadA();
        a.start();
        Thread.sleep(3000);
        for (int i = 0; i < 10; i++) {
            System.out.println("main end get value=" + Tools.t1.get());
            Thread.sleep(1000);
        }
        ;
    }
}

运行结果如下:

main 线程中永远是旧的数据。

6.子线程可以感应对象属性值的变化:可变类型 

        前面都是主线程中使用String数据类型做继承特性的实验,如果子线程从父线程继承可变对象

数据类型,那么子线程可以得到最新对象中的属性值。 

public class InheritableThreadLocal103 {
    static class Userinfo{
        private String username;

        public String getUsername() {
            return username;
        }

        public void setUsername(String username) {
            this.username = username;
        }
    }
    static class Tools{
        public static java.lang.InheritableThreadLocal<Userinfo> t1 = new java.lang.InheritableThreadLocal<>();
    }
    static class ThreadA extends Thread{
        @Override
        public void run() {
            try{
                for (int i = 0; i < 10; i++) {
                    Userinfo userinfo = Tools.t1.get();
                    System.out.println("在ThreadA线程中取值="+userinfo.getUsername()+" "+userinfo.hashCode());
                    Thread.sleep(1000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Userinfo userinfo = new Userinfo();
        System.out.println("A userinfo "+userinfo.hashCode());
        userinfo.setUsername("中国");
        if (Tools.t1.get() == null){
            Tools.t1.set(userinfo);
        }
        System.out.println("    在Main线程中取值="+Tools.t1.get().getUsername()+" "+Tools.t1.get().hashCode());
        Thread.sleep(100);
        ThreadA a = new ThreadA();
        a.start();
        Thread.sleep(5000);
        Tools.t1.get().setUsername("美国");
    }
}

程序运行结果是ThreadA取到了userinfo对象的最新属性值了运行结果如下:

        如果在 main() 方法的最后重新放入一个新的Userinfo 对象,则 ThreadA 线程打印的结果永远是中国。这是因为 ThreadA 永远引用的是中国对应的 Userinfo 对象,并不是新版美国对应的Userinfo 对象,所以依然符合"父线程有最新的值,子线程还是旧值",代码如下:

public static void main(String[] args) throws InterruptedException {
        Userinfo userinfo = new Userinfo();
        System.out.println("A userinfo "+userinfo.hashCode());
        userinfo.setUsername("中国");
        if (Tools.t1.get() == null){
            Tools.t1.set(userinfo);
        }
        System.out.println("    在Main线程中取值="+Tools.t1.get().getUsername()+" "+Tools.t1.get().hashCode());
        Thread.sleep(100);
        ThreadA a = new ThreadA();
        a.start();
        Thread.sleep(5000);
        Userinfo userinfo2 = new Userinfo();
        userinfo2.setUsername("美国");
        System.out.println("B userinfo "+userinfo2.hashCode());
        Tools.t1.set(userinfo2);
    }

运行结果:

7.重写 childValue 方法实现对继承值的加工 

重写 childValue 可以实现 继承的同时对值进行进一步的加工。

创建新的测试用例:

public class InheritableThreadLocal {
    static class InheritableThreadLocalExt extends java.lang.InheritableThreadLocal{
        @Override
        protected Object childValue(Object parentValue) {
            return parentValue+" 我在子线程里面加的哦~!";
        }

        @Override
        protected Object initialValue() {
            return new Date().getTime();
        }
    }
    static class Tools {
        public static InheritableThreadLocalExt t1 = new InheritableThreadLocalExt();
    }

    static class ThreadA extends Thread {
        @Override
        public void run() {
            try {
                for (int i = 0; i < 10; i++) {
                    System.out.println("在ThreadA线程中取值=" + Tools.t1.get());
                    Thread.sleep(100);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        try{
            for (int i = 0; i < 10; i++) {
                if (Tools.t1.get() == null) {
                    Tools.t1.set("此值时main线程放入的!");//在此处执行。
                }
                System.out.println("    在Main线程中取值=" + Tools.t1.get());
                Thread.sleep(100);
            }
            Thread.sleep(5000);
            ThreadA a = new ThreadA();
            a.start();
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

运行结果如图:

        通过重写 childValue() 方法,子线程可以对父线程继承的值进行加工和修改。

        在子线程的任意时刻执行 InheritableThreadLocalExt.set() 方法,使子线程具有最新的值。另外,通过重写 childValue() 方法也会使子线程得到最新的值。这两点的区别在于子线程可以在任意的时间执行 InheritableThreadLocalExt.set() 方法任意次,使自身一直持有新的值,而使用重写 childValue() 方法来实现时,只能在创建子线程时有效,而且仅此一次机会。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

duration~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值