• ThreadLocal 线程隔离怎么做到的


    咱们都知道在多线程的环境下,多个线程访问同一个共享变量会有并发问题,解决方式之一就是每个线程使用自己的本地变量,这个本地变量就是 ThreadLocal 实现的。

    比如下面的代码:

        private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();
    
        public static void main(String[] args) throws Throwable {
    
            Thread threadOne = new Thread(() -> {
                Thread.currentThread().setName("threadOne");
                threadLocal.set("ThreadOne:" + Thread.currentThread().getName());
                System.out.println("线程 One set 后本地变量值为:" + threadLocal.get());
                threadLocal.remove();
                System.out.println("线程 One remove 后本地变量值为:" + threadLocal.get());
            });
    
            Thread threadTwo = new Thread(() -> {
                Thread.currentThread().setName("threadTwo");
                threadLocal.set("ThreadOne:" + Thread.currentThread().getName());
                System.out.println("线程 Two set 后本地变量值为:" + threadLocal.get());
            });
    
            threadOne.start();
            threadTwo.start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    会输出以下内容:

    线程 Two set 后本地变量值为:ThreadOne:threadTwo
    线程 One set 后本地变量值为:ThreadOne:threadOne
    线程 One remove 后本地变量值为:null

    结构

    先来看下结构吧:

    static class ThreadLocalMap {
    
            /**
             * The entries in this hash map extend WeakReference, using
             * its main ref field as the key (which is always a
             * ThreadLocal object).  Note that null keys (i.e. entry.get()
             * == null) mean that the key is no longer referenced, so the
             * entry can be expunged from table.  Such entries are referred to
             * as "stale entries" in the code that follows.
             */
            static class Entry extends WeakReference<ThreadLocal<?>> {
                /** The value associated with this ThreadLocal. */
                Object value;
    
                Entry(ThreadLocal<?> k, Object v) {
                    super(k);
                    value = v;
                }
            }
    
            /**
             * The initial capacity -- MUST be a power of two.
             */
            private static final int INITIAL_CAPACITY = 16;
    
            /**
             * The table, resized as necessary.
             * table.length MUST always be a power of two.
             */
            private Entry[] table;
    
            /**
             * The number of entries in the table.
             */
            private int size = 0;
    
            /**
             * The next size value at which to resize.
             */
            private int threshold; // Default to 0
    }
    
    • 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

    其中 Entry 是 ThreadLocal 实际存储值的地方,相关的注释看上述代码就行。

    再来看下 ThreadLocalMap 在哪使用,可以在 IDEA 中方便看到是在 Thread 类中使用:

    public class Thread implements Runnable {
    
        /* 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
    • 10
    • 11
    • 12

    大概的结构就是下图:
    ThreadLocal 结构
    相互之前的引用关系就借用网上一张图吧:
    ThreadLocal 结构

    源码

    set(T value)

    set(T value) 说起:

        public void set(T value) {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    非常简单的几行代码:

    1. 获取当前线程
    2. 根据当前线程获得一个 ThreadLocalMap
    3. map 不是 null 保存
    4. map 为 null,新建一个 Map

    getMap(Thread t)

        ThreadLocalMap getMap(Thread t) {
            return t.threadLocals;
        }
    
    • 1
    • 2
    • 3

    就是将 threadLocals 这个属性返回

    createMap(Thread t,T firstValue)

        void createMap(Thread t, T firstValue) {
            t.threadLocals = new ThreadLocalMap(this, firstValue);
        }
    
    • 1
    • 2
    • 3

    创建一个 ThreadLocalMap,并将值存到其中

    再说回 set(T value) 方法,不管上面的 getMap(Thread t) 获得的 map 是不是 null,最终都是将 value 存到了当前线程的 threadLocals 中。

    每个线程都是各自存自己的,当然就可以隔离了。

    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();
    }
    
    private T setInitialValue() {
       T value = initialValue();
       Thread t = Thread.currentThread();
       ThreadLocalMap map = getMap(t);
       if (map != null)
           map.set(this, value);
       else
           createMap(t, value);
       return value;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    再来说下 get() 方法,也是类似 set(T value) 的逻辑:

    1. 获得当前线程
    2. 从当前线程的 threadLocals 属性中取
    3. 属性为 null 初始化

    ThreadLocal.ThreadLocalMap threadLocals = null;

    这个需要注意下,刚开始是 null,当线程 exit() 的时候还会被置为 null,见下述代码:

        private void exit() {
            // 省略一些代码
            threadLocals = null;
            inheritableThreadLocals = null;
            // 省略一些代码
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    加餐

    为什么要手动 remove

    可能会有人问,既然线程 exit 的时候会将 threadLocals 置 null,为啥还要手动 remove()呢?那如果我的线程是线程池里呢,一般情况下核心线程是永不结束的。当 threadlocal 被回收后,刚才说的 map 中的 key 就被回收了,但是 map 中的 value 并没有回收,随着没有被回收的 value 越来越多,会造成内存溢出。

    那如果我将 Entry 中的 key 设置为强引用呢,那还是会内存溢出的,因为线程一直没停,threadlocal 就一直被引用着。

    父子进程传递

    可以使用 InheritableThreadLocal 解决

        public static void main(String[] args) {
            ThreadLocal<String> ThreadLocal = new ThreadLocal<>();
            ThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
            ThreadLocal.set("main -> threadLocal");
            inheritableThreadLocal.set("main -> inheritableThreadLocal");
    
            new Thread(() -> {
                System.out.println("子线程获取父类 threadLocal:" + ThreadLocal.get());
                System.out.println("子线程获取父类 inheritableThreadLocal:" + inheritableThreadLocal.get());
            }).start();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    线程池内线程传递

    可以看下:alibaba/TransmittableThreadLocal (TTL)

  • 相关阅读:
    Ubuntu:解决PyCharm中不能输入中文或者输入一个中文解决方法
    JDK1.8 新特性(二)【Stream 流】
    Autoware.universe部署06:使用DBC文件进行UDP的CAN通信代码编写
    Golang开发--互斥锁和读写锁
    unity OnTriggerEnter和OnCollisionEnter生命周期
    Rust 使用Cargo
    1212. 地宫取宝
    Lua学习笔记:require非.lua拓展名的文件
    mac 安装mven& IDEA配置maven环境
    黑苹果N卡显卡驱动,10.13.6
  • 原文地址:https://blog.csdn.net/MrBaymax/article/details/126087320