• 2 ThreadLocal-InheritableThreadLocal


    InheritableThreadLocal

    ThreadLocal线程间的问题:ThreadLocal无法在父子线程间进行传递。例如:主线程设置ThreadLocal,并且创建了新线程,此时新线程访问主线程设置的ThreadLocal,返回null。在线程池中更为突出,因为线程池中的业务线程是复用的,每个任务提交时,都有不同的ThreadLocal。

    为了解决ThreadLocal在父子线程中的传递问题,应运而生了InheritableThreadLocal。ThreadLocal不能解决父子线程间的传递,那么另起个变量InheritableThreadLocal,创建新线程时,复制父线程的InheritableThreadLocal,通过新变量解决上述问题。

    存储位置

     /* 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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    inheritableThreadLocals和threadLocals,都存储在Thread线程中,只不过inheritableThreadLocals可以继承。

    数据结构

    查看InheritableThreadLocal就是继承自ThreadLocal,只不过修改了getMap/createMap方法,即返回不同的存储位置。

    使用示例

    使用范例:

    public static void main(String[] args) {
            InheritableThreadLocal<String> itl = new InheritableThreadLocal<>();
            itl.set("age");
            System.out.println(Thread.currentThread().getName() + ":" + itl.get());
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + ":" + itl.get());
                    itl.set("inheritable");
                    System.out.println(Thread.currentThread().getName() + ":" + itl.get());
    
                }
            }).start();
    
            for (int i = 0; i < 100000; i++) {
    
            }
            System.out.println(Thread.currentThread().getName() + ":" + itl.get());
            itl.set("address");
            System.out.println(Thread.currentThread().getName() + ":" + itl.get());
        }
    输出:
    main:age
    Thread-0:age
    Thread-0:inheritable
    main:age
    main:address
    
    • 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

    通过上述范例,InheritableThreadLocal实现了ThreadLocal在线程中的传递,当然也存在一些缺点。

    生效原因

    创建线程通过new Thread方法,而方法的底层还是调用init方法,看看init方法。

    private void init(ThreadGroup g, Runnable target, String name,
                          long stackSize, AccessControlContext acc) {
            //省略一些无关代码
            //parent=主线程/父线程
            Thread parent = currentThread();
            //省略无关代码
            //父线程的inheritableThreadLocals不为空,copy
            if (parent.inheritableThreadLocals != null)
                this.inheritableThreadLocals =
                    ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
            
        }
    
    //createInheritableMap
    
    static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
            return new ThreadLocalMap(parentMap);
        }
    
    //new ThreadLocalMap(parentMap) 就是把ThreadLocalMap copy一份
    private ThreadLocalMap(ThreadLocalMap parentMap) {
                Entry[] parentTable = parentMap.table;
                int len = parentTable.length;
                setThreshold(len);
                table = new Entry[len];
    
                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);
                            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
    • 42
    • 43

    生效的原因:子线程主动从父线程copy一份InheritableThreadLocal,这样就保证了上下文的传递。

    缺点

    InheritableThreadLocal也存在一些缺陷:

    1. copy是一次性的,只有子线程创建时,才会copy父线程,并且只copy因此。因此创建完子线程后,主线程添加的ThreadLocal,子线程获取仍然为null。
    2. 子线程的InheritableThreadLocal的各种操作,例如:set值,remove值,对主线程都没有影响。
    3. 子线程copy主线程的InheritableThreadLocal是浅拷贝,如果子线程/主线程,针对value的修改,双方都可以感知。优点:可以把通过value的变动,实现线程之间的互通。
      缺点:一方对value的修改,可能会影响另一方。
    4. InheritableThreadLocal对线程池不支持。因为子线程的copy是一次性的,因此当创建池内线程池,会从业务线程copy一份InheritableThreadLocal,但是当池内线程执行其他业务时,就不会copy该业务提交时,业务线程的InheritableThreadLocal,同时可能因为第一次的copy导致后续的InheritableThreadLocal的污染。
    public class InheritableThreadLocalDemo {
        private static ExecutorService executorService = Executors.newFixedThreadPool(1);
        private static InheritableThreadLocal<String> tl = new InheritableThreadLocal();
    
        public static void main(String[] args) {
            for (int i = 0; i < 10; i++) {
                int temp = i;
                //模拟10个请求
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        tl.set("test" + temp);
                        System.out.println(Thread.currentThread().getName() + ":" + tl.get());
                        executorService.execute(new Runnable() {
                            @Override
                            public void run() {
                                System.out.println(Thread.currentThread().getName() + ":" + tl.get());
                            }
                        });
                    }
                }).start();
    
            }
        }
    
    }
    
    Thread-0:test0
    Thread-5:test5
    Thread-4:test4
    Thread-2:test2
    Thread-1:test1
    Thread-8:test8
    Thread-9:test9
    Thread-3:test3
    Thread-6:test6
    Thread-7:test7
    pool-1-thread-1:test6
    pool-1-thread-1:test6
    pool-1-thread-1:test6
    pool-1-thread-1:test6
    pool-1-thread-1:test6
    pool-1-thread-1:test6
    pool-1-thread-1:test6
    pool-1-thread-1:test6
    pool-1-thread-1:test6
    pool-1-thread-1:test6
    
    • 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

    通过输出发现,10个请求,都各自设置了自己的TTL,但是最终池内线程,只输出一个tl,这就是数据污染,因为pool-1-thread-1线程是Thread-6创建的,因此copy test6,当池线程执行其他的任务时,拿到的都是test6。

    说明:为了解决InheritableThreadLocal在线程池中的缺陷,衍生了:TransmittableThreadLocal

  • 相关阅读:
    电脑重装系统后如何设置 win11 的默认登录方式
    mmdetection最新版食用教程(一):安装并运行demo及开始训练coco
    Web前端开发的五大核心技能
    k8s网络插件之Calico
    Cocos2d Opengl2.1 升级到 opengl3.3
    使用Docker中部署GitLab 避坑指南
    Flutter 游戏教程之使用 Flutter 和 Flame 重现著名的 T-Rex 游戏
    【vue功能】多张图片合并
    Gateway的服务网关
    【Python】Pandas处理数据太慢,来试试Polars吧!
  • 原文地址:https://blog.csdn.net/u010652576/article/details/126925264