• ThreadLocal



    前言

    ThreadLocal 是线程本地变量。当使用 ThreadLocal 维护变量时,ThreadLocal 为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程。


    一、ThreadLocal 使用场景

    主要有以下三种场景。

    • 作为数据副本,当某些数据是以线程为作用域并且不同线程有不同数据副本时,可以考虑 ThreadLocal。
    • 保存线程上下文信息,在任意需要的地方可以获取,避免显示传参。
    • 解决线程安全问题,避免某些情况需要考虑线程安全必须同步带来的性能损失。
    • 在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
    • 线程间数据隔离
    • 进行事务操作,用于存储线程事务信息
    • 数据库连接,Session会话管理

    二、ThreadLocal的原理和实现

    • 每个线程都有一个 ThreadLocalMap(ThreadLocal内部类),Map 中元素的键为ThreadLocal,而值对应线程的变量副本。Map是数组实现,使用线性探测解决hash冲突,需要手动调用set、get、remove防止内存泄漏。
    • ThreadLoal 变量,线程局部变量,同一个 ThreadLocal 所包含的对象,在不同的 Thread 中有不同的副本。ThreadLocal 变量通常被private static修饰。当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。
    • 一个线程内可以存在多个 ThreadLocal 对象,所以其实是 ThreadLocal 内部维护了一个 Map ,这个 Map不是直接使用的 HashMap ,而是 ThreadLocal 实现的一个叫做 ThreadLocalMap 的静态内部类。而我们使用的get()、set() 方法其实都是调用了这个ThreadLocalMap类对应的 get()、set() 方法。

    2.ThreadLocal怎么用?

    代码如下(示例):

    public class ThreadLocalTest02 {
    
        public static void main(String[] args) {
    
            ThreadLocal<String> local = new ThreadLocal<>();
    
            IntStream.range(0, 10).forEach(i -> new Thread(() -> {
                local.set(Thread.currentThread().getName() + ":" + i);
                System.out.println("线程:" + Thread.currentThread().getName() + ",local:" + local.get());
            }).start());
        }
    }
    
    输出结果:
    线程:Thread-0,local:Thread-0:0
    线程:Thread-1,local:Thread-1:1
    线程:Thread-2,local:Thread-2:2
    线程:Thread-3,local:Thread-3:3
    线程:Thread-4,local:Thread-4:4
    线程:Thread-5,local:Thread-5:5
    线程:Thread-6,local:Thread-6:6
    线程:Thread-7,local:Thread-7:7
    线程:Thread-8,local:Thread-8:8
    线程:Thread-9,local:Thread-9:9
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    从结果可以看到,每一个线程都有自己的local 值,这就是TheadLocal的基本使用 。

    下面我们从源码的角度来分析一下,ThreadLocal的工作原理。


    3.ThreadLocal源码分析

    set 方法

    /**
         * Sets the current thread's copy of this thread-local variable
         * to the specified value.  Most subclasses will have no need to
         * override this method, relying solely on the {@link #initialValue}
         * method to set the values of thread-locals.
         *
         * @param value the value to be stored in the current thread's copy of
         *        this thread-local.
         */
        public void set(T value) {
            //首先获取当前线程对象
            Thread t = Thread.currentThread();
            //获取线程中变量 ThreadLocal.ThreadLocalMap
            ThreadLocalMap map = getMap(t);
            //如果不为空,
            if (map != null)
                map.set(this, value);
            else
                //如果为空,初始化该线程对象的map变量,其中key 为当前的threadlocal 变量
                createMap(t, value);
        }
    
        /**
         * Create the map associated with a ThreadLocal. Overridden in
         * InheritableThreadLocal.
         *
         * @param t the current thread
         * @param firstValue value for the initial entry of the map
         */
    //初始化线程内部变量 threadLocals ,key 为当前 threadlocal
        void createMap(Thread t, T firstValue) {
            t.threadLocals = new ThreadLocalMap(this, firstValue);
        }
    
           /**
             * Construct a new map initially containing (firstKey, firstValue).
             * ThreadLocalMaps are constructed lazily, so we only create
             * one when we have at least one entry to put in it.
             */
            ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
                table = new Entry[INITIAL_CAPACITY];
                int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
                table[i] = new Entry(firstKey, firstValue);
                size = 1;
                setThreshold(INITIAL_CAPACITY);
            }
    
    
     static class Entry extends WeakReference<ThreadLocal<?>> {
                /** The value associated with this ThreadLocal. */
                Object value;
    
                Entry(ThreadLocal<?> k, Object v) {
                    super(k);
                    value = v;
                }
            }
    
    • 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

    汇总下,ThreadLocalMap 为 ThreadLocal 的一个静态内部类,里面定义了Entry 来保存数据。而且是继承的弱引用。在Entry内部使用ThreadLocal作为key,使用我们设置的value作为value。

    对于每个线程内部有个ThreadLocal.ThreadLocalMap 变量,存取值的时候,也是从这个容器中来获取。

    get方法

    /**
         * Returns the value in the current thread's copy of this
         * thread-local variable.  If the variable has no value for the
         * current thread, it is first initialized to the value returned
         * by an invocation of the {@link #initialValue} method.
         *
         * @return the current thread's value of this thread-local
         */
        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
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    通过上面的分析,相信你对该方法已经有所理解了,首先获取当前线程,然后通过key threadlocal 获取 设置的value 。

    4.避免ThreadLocal 内存泄漏问题

    使用完ThreadLocal后,执行remove操作,避免出现内存溢出情况。

    所以 如同 lock 的操作 最后要执行解锁操作一样,ThreadLocal使用完毕一定记得执行remove 方法,清除当前线程的数值。

    如果不remove 当前线程对应的VALUE ,就会一直存在这个值。

    使用了线程池,可以达到“线程复用”的效果。但是归还线程之前记得清除ThreadLocalMap,要不然再取出该线程的时候,ThreadLocal变量还会存在。这就不仅仅是内存泄露的问题了,整个业务逻辑都可能会出错。

  • 相关阅读:
    浅谈 Class.forName() 的用法
    Core-1684JD4八核高算力AI核心板
    TCK、TMS、TDI、TDO的含义
    SSM+甘肃旅游系统 毕业设计-附源码211707
    js获取对象值得方式(在不确定对象参数的情况下获取值)
    【Vue】07 绑定样式 条件渲染
    Log日志详解分析
    基于开源IM即时通讯框架MobileIMSDK:RainbowChat v10.0版已发布
    day44-反射03
    TiDB ——TiKV
  • 原文地址:https://blog.csdn.net/weixin_43480441/article/details/127589422