Java多线程技术二:线程间通信——InheritableThreadLocal的使用

1 概述

        使用InheritableThreadLocal可以在子线程中取得父线程继承下来的值。

2 ThreadLocal类不能实现值的继承

public class Tools {
    public  static ThreadLocal t1 = new ThreadLocal();
}
public 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 class Run1 {
    public static void main(String[] args) {
        try {
            for (int i = 0; i < 10; i++) {
                if(Tools.t1.get() == null){
                    Tools.t1.set("在main方法中,set的值");
                }
                System.out.println("在main方法中取值 = " + Tools.t1.get());
                Thread.sleep(100);
            }
            Thread.sleep(5000);
            ThreadA a = new ThreadA();
            a.start();
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

Java多线程技术二:线程间通信——InheritableThreadLocal的使用_第1张图片

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

3 使用InheritableThreadLocal体现值继承特性

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

public class Tools {
    public static InheritableThreadLocal t1 = new InheritableThreadLocal();
}
public 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 class Run1 {
    public static void main(String[] args) {
        try {
            for (int i = 0; i < 10; i++) {
                if(Tools.t1.get() == null){
                    Tools.t1.set("在main方法中,set的值");
                }
                System.out.println("在main方法中取值 = " + Tools.t1.get());
                Thread.sleep(100);
            }
            Thread.sleep(5000);
            ThreadA a = new ThreadA();
            a.start();
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

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

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

        使用InheritableThreadLocal的确可以实现值继承的特性,那么在JDK的源码中是如何实现的呢?下面按步骤来分析。

        1、首先看一下InheritableThreadLocal类的源码:

public class InheritableThreadLocal extends ThreadLocal {
    
    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类的源码中有3个方法,这3个方法都是对父类ThreadLocal中的同名方法进行重写后得到的,因为在源码中并没有使用@override进行标识,所以在初期分析时如果不注意,流程是比较绕的。ThreadLocal类中的三个方法的源码:

    T childValue(T parentValue) {
        throw new UnsupportedOperationException();
    }

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

        从上面的源码中可以看出,ThreadLocal类操作的是threadLocals实例变量,而InheritableThreadLocal类操作的是inheritableThreadLocal实例变量,这是2个变量。

        2、在main方法中使用main线程执行InheritableThreadLocal.set()方法。
 

 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.java类中的set()方法时,有两个方法已经被InheritableThreadLocal类重写了,分别是getMap()和createMap()。一定要留意,在执行这两个方法时,调用的是InheritableThreadLocal类中重写的getMap和createMap方法。

        3、通过查看InheritableThreadLocal类中的getMap和createMap方法的源码,可以明确一个重要的知识点,那就是不再像Thread类中的ThreadLocal.ThreadLocalMap threadLocals存入数据了,而是在ThreadLocal.ThreadLocalMap inheritableThreadLocals 存入数据,这两个对象在Thread.java类中的声明如下:

/* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /*
     * InheritableThreadLocal values pertaining to this thread. This map is
     * maintained by the InheritableThreadLocal class.
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

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

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

public
class Thread implements Runnable {

    private void init(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 doesn't have a strong opinion of 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(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 */
        tid = nextThreadID();
    }
}

        因为init()方法是被Thread的构造方法调用的,所以在new ThreadA()中,在Thread.java源码内部会自动调用init方法。在init()方法中的最后一个参数inheritThreadLocals 代表当前线程对象是否会从父线程中继承值。因为这个值被永远传入true,所以每次都会继承值。

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

public class Tools {
    public static InheritableThreadLocal t1 = new InheritableThreadLocal();
}
public 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 class Run1 {
    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(1000);
        ThreadA a = new ThreadA();
        a.start();
        Thread.sleep(5000);
        Tools.t1.set("此值是在main线程 new 放入的");
    }
}

Java多线程技术二:线程间通信——InheritableThreadLocal的使用_第2张图片

        从运行结果可见,子线程还是持有旧的数据。

6 子线程有最新的值,父线程是旧值:不可变类型

       

public class Tools {
    public static InheritableThreadLocal t1 = new InheritableThreadLocal();
}
public 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线程中写入的");
                    System.out.println("ThreadA已经存在最新值---------");
                }
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}
public class Run1 {
    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线程结束,获取的值 = " + Tools.t1.get());
            Thread.sleep(1000);
        }
    }
}

Java多线程技术二:线程间通信——InheritableThreadLocal的使用_第3张图片

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

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

        前面都是在主、子线程中使用String数据类型做继承特性的实验,如果子线程从父线程继承可变对象数据类型,那么子线程可以得到最新对象中的属性值。

public class UserInfo {
    private String username;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }
}

 

public class Tools {
    public static InheritableThreadLocal t1 = new InheritableThreadLocal<>();
}
public class ThreadA extends Thread{
    @Override
    public void run(){
        try {
            for (int i = 0; i < 10; i++) {
                UserInfo userInfo = Tools.t1.get();
                System.out.println("在线程a中取值 = " + userInfo.getUsername() +
                        ";" + userInfo.hashCode());
                Thread.sleep(1000);
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}
public class Run1 {
    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()
         + ";" + Tools.t1.get().hashCode());
        Thread.sleep(100);
        ThreadA a = new ThreadA();
        a.start();
        Thread.sleep(4000);
        Tools.t1.get().setUsername("美国");
    }
}

Java多线程技术二:线程间通信——InheritableThreadLocal的使用_第4张图片

        程序运行结果就是ThreadA取到userinfo对象的最新属性值。如果在main方法的最后重新放入一个新的UserInfo对象,则ThreadA线程打印的结果永远是中国。这是因为ThreadA永远引用的是中国对应的UserInfo对象,并不是新版美国魅影的UserInfo对象,所以依然符合“父线程有最新的值,子线程还是旧值” 

你可能感兴趣的:(java,java,jvm,开发语言)