ThreadLocal了解

文章目录

    • 概述
    • 源码
    • 原理
    • 内存泄露
    • 应用场景

概述

  • ThreadLocal提供线程的局部变量,这种变量与普通变量的区别在于,每个访问这种变量的线程都有自己的、独立的变量副本。用于解决多线程间的数据隔离问题。

源码

//返回Thread实例的成员变量threadLocals
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

ThreadLocals 依赖于附加到每个线程的每线程线性探测哈希映射(Thread.threadLocals 和 inheritableThreadLocals)。ThreadLocal 对象充当键,通过 threadLocalHashCode 进行搜索。这是一个自定义哈希代码(仅在 ThreadLocalMap 中有用),它消除了相同线程使用连续构造的 ThreadLocals 的常见情况下的冲突,同时在不太常见的情况下保持良好的行为。

get方法

返回此线程局部变量的当前线程副本中的值。如果变量没有当前线程的值,则首先将其初始化为调用该方法返回 initialValue 的值。

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();
}

set方法

将此线程局部变量的当前线程副本设置为指定值。大多数子类不需要重写此方法,仅依靠该方法 initialValue 来设置线程局部变量的值。

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

remove方法

删除此线程局部变量的当前线程值。如果此线程局部变量随后由当前线程 读取 ,则其值将通过调用其方法重新初始化,除非其 initialValue 值在此期间由当前线程 设置 。这可能会导致在当前线程中多次调用 initialValue 该方法。

public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        m.remove(this);
}

原理

多线程隔离

  • ThreadLocal提供线程局部变量。这些变量与普通对应变量的不同之处在于,访问一个变量的每个线程(通过其 get 或 set 方法)都有自己独立初始化的变量副本。ThreadLocal 实例通常是希望将状态与线程(例如,用户 ID 或事务 ID)相关联的类中的私有静态字段。
  • 只要线程处于活动状态并且 ThreadLocal 实例可访问,每个线程都包含对其线程局部变量副本的隐式引用;线程消失后,其线程本地实例的所有副本都将受到垃圾回收(除非存在对这些副本的其他引用)。

ThreadLocalMap

  • ThreadLocalMap 是一个自定义的哈希映射,仅适用于维护线程本地值。不会在 ThreadLocal 类外部导出任何操作。该类是包私有的,允许在类 Thread 中声明字段。为了帮助处理非常大且长期存在的用法,哈希表条目对键使用 WeakReferences。但是,由于不使用引用队列,因此仅当表开始空间不足时,才能保证删除过时的条目。

内存泄露

  • ThreadLocalMap中的key是弱引用,而value是强引用。所以,如果ThreadLocal没有被外部强引用的情况下,在垃圾回收时,key会被清空掉,而value不会。ThreadLocal中就会出现key为null的Entry。假如我们不采取措施,value永远不会被GC回收,这个时候就可能会产生内存泄漏问题。
  • 解决办法:使用完ThreadLocal后,执行remove操作,避免出现内存溢出情况。

为什么key使用弱引用?

  • 如果使用强引用,当ThreadLocal 对象的引用(强引用)被回收了,ThreadLocalMap本身依然还持有ThreadLocal的强引用,如果没有手动删除这个key ,则ThreadLocal不会被回收,所以只要当前线程不消亡,ThreadLocalMap引用的那些对象就不会被回收, 可以认为这导致Entry内存泄漏。

强引用:普通的引用,强引用指向的对象不会被回收;
软引用:仅有软引用指向的对象,只有发生gc且内存不足,才会被回收;
弱引用:仅有弱引用指向的对象,只要发生gc就会被回收。

应用场景

  • 数据库事务:通过AOP的方式,对执行数据库事务的函数进行拦截。函数开始前,获取connection开启事务并存储在ThreadLocal中,任何用到connection的地方,从ThreadLocal中获取。函数执行完毕,提交事务释放connection。
  • 用户登录信息:用户登录信息好多个方法上都要用到,给每个方法都添加一个User非常麻烦,而且有些时候,如果调用链有无法修改源码的第三方库,User对象就传不进去了。而ThreadLocal可以在一个线程中传递同一个对象。
  • RequestContextHolder:Spring 提供的一个用来暴露 Request 对象的工具,利用 RequestContextHolder,可以在一个请求线程中获取到 Request,避免了 Request 从头传到尾的情况。一般项目中,会对这个类再次进行封装,便于获取请求的相关的信息,常见的比如用户信息。

总结起来使用的场景就是一下这些地方:

在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
线程间数据隔离
进行事务操作,用于存储线程事务信息。
数据库连接,Session会话管理。

你知道的越多,你不知道的越多。

你可能感兴趣的:(java基础,java,数据结构,jvm,ThreadLocal,多线程)