InheritableThreadLocal

请直接看原文:

InheritableThreadLocal详解-CSDN博客

-------------------------------------------------------------------------------------------------------------------------------- 

自己简单总结:

1.InheritableThreadLocal继承了ThreadLocal

2.InheritableThreadLocal有ThreadLocal的所有特点

3.唯一不同点是,new新线程的时候, 新线程的InheritableThreadLocal可以继承上一个线程的InheritableThreadLocal的值.

前言

使用类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);

到此,主线程就把 inheritableThreadLocals赋值给了子线程 的 inheritableThreadLocals

下面是继续深入分析

7)代码 this.inheritableThreadLocals 中的 this 就是当前 Thread.java 类的对象,执行 createInheritedMap() 方法的目的是先创建一个新的 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 数据类型可以是不可变的,也可以是可变的,因此会出现两种截然不同的结果。

做实验

1.不可变数据类型:

指string/八种基本数据类型及其包装类

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

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赋新的值常量值会开辟新的内存空间。

2.可变数据类型:

指的是实体类

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

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 中的属性值,子线程就能感应到。

3.InheritableThreadLocal传不可变数据类型

指string/八种基本数据类型及其包装类

结论: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(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(1000);
        Tools.t1.set("此值是main线程 newnewnewnew 放入的");
    }
}

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

 情况二:实验得知: 子线程具有最新的值,父线程还是旧值 

测试代码:

 
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 线程中永远是旧的数据。

4.InheritableThreadLocal传可变数据类型

指的是实体类

结论:InheritableThreadLocal传可变数据类型时, 当父线程改变这个对象里的属性值,那么子线程也能获得这个对象的属性的最新的值. 

但是,如果父线程直接set了一个new的新对象,新对象有新的内存地址,子线程是获取不到这个新对象的.因此子线程的值还是对应原来的旧对象.

下面是两种情况的实验:

情况一:父线程改变这个对象里的属性值,那么子线程也能获得这个对象的属性的最新的值. 

测试代码:

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对象的最新属性值了运行结果如下:

情况二:如果父线程直接set了一个new的新对象,新对象有新的内存地址,子线程是获取不到这个新对象的.因此子线程的值还是对应原来的旧对象.

测试代码:

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

运行结果:

 同理,做测试得知:子线程改变了对象里的属性值,父线程是可以看到新的属性值的,  子线程如果new 了一个新的对象,然后set了这个对象, 那么父线程是获得不到子线程新new 的对象的.

重写 childValue 方法和initialValue方法

原本的childValue方法,在子类复制父类threadlocalmap时执行, 父类是什么数据,子类就复制什么数据;  重写后,如下面代码,我们可以在复制的值后面加一句自定义的话"我在子线程里加的哦!"

原本的initialValue方法,当local调用get()方法,发现local并没有被set()值时,返回null;  重写后,如下面代码,我们可以让它返回当前时间搓.

因此子线程复制父线程数据时, 已经执行了set方法,给了值,  因此 initialValue方法并不会被执行.

创建新的测试用例:

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

运行结果如图:

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值