• ThreadLocal和InheritableThreadLocal实现原理


    ThreadLocal实现原理

    // ThreadLocal.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);
    }
    
    
    
    //ThreadLocalMap保存在Thread中:
     
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    可见threadLocals为Thread的内部变量,每个Thread类的实例,都有自己的threadLocals变量

    // Thread类源码
    public class Thread implements Runnable {
      	......
        ThreadLocal.ThreadLocalMap threadLocals = null;
        ......
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    总结
    ThreadLocal属性保存在每个线程实例中,线程实例通过Map结构管理所有的ThreadLocal属性,Map的key为ThreadLocal属性。

    InheritableThreadLocal实现原理

    ThreadLocal属性是Thread的内部变量,父子线程是不同的线程实例,显然在子线程中无法获取父线程设置的属性:

    public class MainToChild implements Runnable {
     
        public static ThreadLocal<String> threadLocal = new ThreadLocal();
     
        @Override
        public void run() {
            System.out.println("child : " + Thread.currentThread().getName());
            System.out.println("child : " + threadLocal.get());
        }
     
        public static void main(String[] args) {
     
            MainToChild mainToChild = new MainToChild();
            Thread thread = new Thread(mainToChild);
            thread.start();
     
            threadLocal.set("main");
     
            System.out.println("main : " + Thread.currentThread().getName());
            System.out.println("main : " + threadLocal.get());
        }
    }
    
    ---------------------------------------------
    main : main
    child : Thread-0
    child : null
    main : main
    
    • 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

    ThreadLocal属性是Thread的内部变量,父子线程是不同的线程实例,显然在子线程中无法获取父线程设置的属性

    InheritableThreadLocal实现父子进程传递

    public class MainToChild implements Runnable {
     
        public static InheritableThreadLocal<String> threadLocal = new InheritableThreadLocal();
     
        @Override
        public void run() {
            System.out.println("child name : " + Thread.currentThread().getName());
            System.out.println("child get main value : " + threadLocal.get());
            threadLocal.set("child");
            System.out.println("child value : " + threadLocal.get());
     
        }
     
        public static void main(String[] args) {
     
            threadLocal.set("main");
     
            MainToChild mainToChild = new MainToChild();
            Thread thread = new Thread(mainToChild);
            thread.start();
     
            System.out.println("main name : " + Thread.currentThread().getName());
            System.out.println("main value: " + threadLocal.get());
        }
    }
    
    
    
    
    ---------------------------------------------
    main name : main
    child name : Thread-0
    child get main value : main
    main value: main
    child value : child
    
    • 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

    InheritableThreadLocal改变初始值后子进程无法获取新值

    package com.sankuai.mall.consumerapi.web.controller;
     
    import lombok.SneakyThrows;
     
    /**
     * @author : mazhen
     * Date    : 2021/11/12
     * Time    : 3:11 下午
     * ---------------------------------------
     * Desc    :
     */
    public class MainToChild implements Runnable {
     
        public static InheritableThreadLocal threadLocal = new InheritableThreadLocal();
     
        @SneakyThrows
        @Override
        public void run() {
            System.out.println("child name : " + Thread.currentThread().getName());
            System.out.println("1 child get main value : " + threadLocal.get());
            System.out.println("2 child get main value : " + threadLocal.get());
        }
     
        public static void main(String[] args) {
     
            threadLocal.set("main");
     
            MainToChild mainToChild = new MainToChild();
            Thread thread = new Thread(mainToChild);
            thread.start();
     
            threadLocal.set("again main");
     
            System.out.println("main name : " + Thread.currentThread().getName());
            System.out.println("main value: " + threadLocal.get());
        }
     
    }
    
    
    子进程无法获取新值
    ---------------------------------------------
    child name : Thread-0
    main name : main
    1 child get main value : main
    main value: again main
    2 child get main value : main
    
    • 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

    改为线程池的方式也是一样的效果

    package com.sankuai.mall.consumerapi.web.controller;
     
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
     
    /**
     * @author : mazhen
     * Date    : 2021/11/12
     * Time    : 4:11 下午
     * ---------------------------------------
     * Desc    :
     */
    public class ITL {
     
        public static InheritableThreadLocal<String> param = new InheritableThreadLocal<>();
     
        public static void main(String[] args) {
            param.set("main");
     
            ExecutorService pool = Executors.newFixedThreadPool(1);
            pool.submit(() -> {
                System.out.println(param.get());
            });
            param.set("main2");
            pool.submit(() -> {
                System.out.println(param.get());
            });
            pool.submit(() -> {
                System.out.println(param.get());
            });
            pool.shutdown();
        }
     
    }
    
    
    
    线程池只能获取初始值,无法获取新值
    ---------------------------------------------
    main
    main
    main
    
    • 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

    InheritableThreadLocal 实现原理

    // Thread类源码
    public class Thread implements Runnable {
      	......
    	ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
        ......
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    数据结构ThreadLocalMap和threadLocal是一样的

    InheritableThreadLocal及其局限性
    Jdk提供了InheritableThreadLocal类,用于在父子线程间传递线程变量(ThreadLocal),实现原理就是在Thread类保存名为inheritableThreadLocals的成员属性(以InheritableThreadLocal对象为Key的ThreadLocalMap),并在初始化创建子线程时,将父线程的inheritableThreadLocals赋给子线程,这部分逻辑在Thread.init()方法内。

    //只会在初始化一次,设置为父类的变量值
    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);
      		.....
    }
     
    static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
            return new ThreadLocalMap(parentMap);
    }
    
    //初始化时会把父类的线程变量遍历的拷贝过来
    private ThreadLocalMap(ThreadLocalMap parentMap) {
           Entry[] parentTable = parentMap.table;//父类的table
           int len = parentTable.length;
           setThreshold(len);
           table = new Entry[len];//当前类的table长度和父类相同
     
           for (int j = 0; j < len; j++) {
               Entry e = parentTable[j]; //遍历父类table
               if (e != null) {
                   @SuppressWarnings("unchecked")
                   ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                   if (key != null) {
                        // 需要注意childValue方法,通过重写这个方法,
                        // 可以实现自定义的拷贝逻辑
                        Object value = key.childValue(e.value);
                        Entry c = new Entry(key, value);
                        int h = key.threadLocalHashCode & (len - 1);
                        while (table[h] != null)
                                h = nextIndex(h, len);
                        table[h] = c;
                        size++;
                   }
               }
           }
    }
    
    • 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

    这种方式在线程只被创建和使用一次时是有效的,但对于使用线程池的场景下,由于线程被复用,初始化一次后,后续修改变量,子线程并不会再更新初始值,导致后续提交的任务并不会获取到父线程的线程变量最新值,同时,还会获取到当前线程之前的初始变量值。

  • 相关阅读:
    应急监管双重预防机制数字化管理解决方案
    Nginx 前端 安装,打包,发布项目到服务 流程
    【MySQL】MySQL数据库锁使用与InnoDB加锁的原理解析
    谁来看看这个算法解答这个问题的解题过程!
    Debian10 安装mysql
    VUE之更换背景颜色
    变电站运维服务方案
    简单Wordpress小工具开发
    智云通CRM:如何将客户回绝扭转乾坤?
    JavaScript - canvas - 选择部分区域的图像数据
  • 原文地址:https://blog.csdn.net/zjcjava/article/details/125602742