• InheritableThreadLocal使用详解


    一、InheritableThreadLocal的使用

    InheritableThreadLocal用于子线程继承父线程的数值。将通过重写initialValue() 与childValue(Object parentValue)两个方法来展示例子。
    其中initialValue()是InheritableThreadLocal类继承于ThreadLocal类的,用于初始化当前线程私有初始值,childValue(Object parentValue)是InheritableThreadLocal类的,作用是继承父线程的初始值并且进一步处理。

    使用示例

    
    public class test {
    
        /**
         * 静态调用IhThreadLocal
         */
        private static test.IThreadLocal iThreadLocal = new test.IThreadLocal();
    
        /**
         * 父线程输出IhThreadLocalUse的值与分两次创建子线程查看内IhThreadLocalUse的值
         *
         * @throws InterruptedException
         */
        public static void main(String[] args) throws InterruptedException {
    
            // 情况1:在父类初始化IhThreadLocal前子线程先执行子线程
            test.Ithread iThread1 = new test.Ithread();
            iThread1.start();
            Thread.sleep(1000);
    
            // 情况2:在父线程初始化IhThreadLocal后执行子线程
            for (int i = 0; i < 2; i++) {
                System.out.println(iThreadLocal.get());
            }
            test.Ithread iThread2 = new test.Ithread();
            iThread2.start();
        }
    
        /**
         * 继承InheritableThreadLocal方法,并且重写初始化线程数值方法与修改子线程数值方法
         */
        static class IThreadLocal extends InheritableThreadLocal {
    
            @Override
            protected Object initialValue() {
                return LocalDateTime.now();
            }
    
            @Override
            protected Object childValue(Object parentValue) {
                return "子线程获取父线程传递数据:" + parentValue;
            }
        }
    
        /**
         * 输出子线程的IhThreadLocalUse的值
         */
        static class Ithread extends Thread {
            @SneakyThrows
            @Override
            public void run() {
                for (int i = 0; i < 2; i++) {
                    System.out.println(iThreadLocal.get());
                    Thread.sleep(100);
                }
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    输出结果:

    2022-09-18T12:42:47.361
    2022-09-18T12:42:47.361
    2022-09-18T12:42:48.312
    2022-09-18T12:42:48.312
    子线程获取父线程传递数据:2022-09-18T12:42:48.312
    子线程获取父线程传递数据:2022-09-18T12:42:48.312
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    结论

    1. 通过情况1和结果可以看出,子线程继承父线程值时,得父线程已经初始化过值后,否则子线程则自身调用initialValue()来初始化数值,并且不走childParent方法,此时与使用ThreadLocal(用于声明每个线程自身独有的值)无异。
    2. 子线程在父线程已经初始化值的情况下,不调用initiaValue()方法来初始化值,而是走childValue来返回数值,无论是否重写过该方法,因为该方法本身就是返回父线程的数值。下面是该方法的源码,可以看到是返回parentValue的值。

    二、InheritableThreadLocal原理

    为什么InheritableThreadLocal 的子线程可以继承父线程值?

    InheritableThreadLocal的源代码:

    public class InheritableThreadLocal<T> extends ThreadLocal<T> {
     
        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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    这个类继承了ThreadLocal,并且重写了getMap和createMap方法,区别就是将 ThreadLocal 中的 threadLocals 换成了 inheritableThreadLocals,这两个变量都是ThreadLocalMap类型,并且都是Thread类的属性。

    下面就一步步来看下InheritableThreadLocal为什么能拿到父线程中的ThreadLocal值。
    step1:InheritableThreadLocal获取值先调用了ThreadLocal的get方法,所以我们直接看看get方法都做了些啥。

    public T get() {
    	Thread t = Thread.currentThread();
    	    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();
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    从上面的代码可以看出,get方法和ThreadLocal中是一样的,唯一有区别的就是其中的getMap方法重写了,返回的是inheritableThreadLocals属性。这个属性也是一个ThreadLocalMap类型的变量。那么从这边就可以推断出来:肯定是在某处将父线程中的ThreadLocal值赋值到了子线程的inheritableThreadLocals中。

    step2:在源代码中搜索哪些地方使用到了inheritableThreadLocals这个属性,最后找到Thread的init()代码

    
        /**
         * Initializes a Thread.
         *
         * @param g                   线程组
         * @param target              run() 方法被调用的对象
         * @param name                新线程的名称
         * @param stackSize           新线程所需的堆栈大小,或为零表示要忽略此参数。
         * @param acc                 要继承的 AccessControlContext,如果为 null,则为 AccessController.getContext()
         * @param inheritThreadLocals 如果 {@code true},从构造线程继承可继承线程局部变量的初始值
         */
        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) {
                if (security != null) {
                    g = security.getThreadGroup();
                }
                if (g == null) {
                    g = parent.getThreadGroup();
                }
            }
            g.checkAccess();
            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);
            
            // 第一项inheritThreadLocals 是传进来的boolean值,重载时传的是true,
            // 第二项就是判断父线程中的inheritableThreadLocals属性是否为空,不为空的话
            // 两个条件同时满足,把父线程的inheritableThreadLocals复制给子线程
            if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            	// 则新创建出来的子线程的inheritableThreadLocals 变量就和父线程的inheritableThreadLocals 的内容一样了。
                this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
         
            this.stackSize = stackSize;
          
            tid = nextThreadID();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    三、InheritableThreadLocal和线程池搭配使用存在的问题

    InheritableThreadLocal和线程池搭配使用时,可能得不到想要的结果,因为线程池中的线程是复用的,并没有重新初始化线程,InheritableThreadLocal之所以起作用是因为在Thread类中最终会调用init()方法去把InheritableThreadLocal的map复制到子线程中。由于线程池复用了已有线程,所以没有调用init()方法这个过程,也就不能将父线程中的InheritableThreadLocal值传给子线程。

    
    public class test {
    
        private static ExecutorService executorService = Executors.newFixedThreadPool(1);
        private static InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
    
        public static void main(String[] args) throws InterruptedException {
            // 主线程第一次赋值
            inheritableThreadLocal.set("first set1");
            System.out.println("父线程第一次获取值:" + inheritableThreadLocal.get());
    
            executorService.submit(new Ithread());
            Thread.sleep(2000);
    
            inheritableThreadLocal.set("second set2");
            System.out.println("父线程第二次获取值:" + inheritableThreadLocal.get());
    
            executorService.submit(new Ithread());
            executorService.shutdown();
    
        }
    
        /**
         * 输出子线程的IhThreadLocalUse的值
         */
        static class Ithread extends Thread {
            @SneakyThrows
            @Override
            public void run() {
                System.out.println("子线程值:" + inheritableThreadLocal.get());
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    结果:

    父线程第一次获取值:first set1
    子线程值:first set1
    父线程第二次获取值:second set2
    子线程值:first set1
    
    • 1
    • 2
    • 3
    • 4

    可以看出,我们在父线程中第二次set并没有被第二次submit的线程get到。

    四、解决线程池传递值问题,见TransmittableThreadLocal

    TransmittableThreadLocal

  • 相关阅读:
    Enzo丨Enzo AMPIVIEW HPV 6/11 RNA探针组方案
    入门深度学习—从配置python到网络模型
    中学校园IP网络广播系统解决方案-校园数字IP广播系统建设指南
    设备监理师证书含金量怎样?值得考吗?
    Scrum敏捷开发方法
    java基于springboot医院预约挂号管理系统附代码段
    Git使用教程
    在LangChain中使用Milvus + openai使用
    【云原生 | Kubernetes 系列】----使用Prometheus监控K8s集群
    数据科学家赚多少?数据全分析与可视化 ⛵
  • 原文地址:https://blog.csdn.net/wangnanwlw/article/details/126916053