ThreadLocal的使用和坑点

简介: ThreadLocal的使用和坑点

概念:

ThreadLocal的概念:摘自ThreaLocal的注释

This class provides thread-local variables.  These variables differ from
 * their normal counterparts in that each thread that accesses one (via its
 * {@code get} or {@code set} method) has its own, independently initialized
 * copy of the variable.  {@code ThreadLocal} instances are typically private
 * static fields in classes that wish to associate state with a thread (e.g.,
 * a user ID or Transaction ID).

这段话的大概意思是ThreadLocal是保存的线程的本地变量,访问get/set方法都是对线程独立的。
大白话就是ThreadLocal是和线程相关的,在一个线程没有结束之前,在任意方法中get/set在ThreadLocal中设置的值都是只和当前线程有关。
因此呢,ThreadLocal的使用场景也可以推测出来,可以用来在一个线程中传递参数,或者某些情况(比如session,数据库操作句柄)只跟线程相关的时候来使用。

源码分析:

我们接下来在看看源代码中,ThreadLocal是什么样的?
简答的贴了一下类和常用的几个方法的源代码(此博客是基于JDK1.8)
类定义:

public class ThreadLocal<T>

从类定义上可以看出ThreadLocal是支持泛型的

ThreadLocalMap:

static class ThreadLocalMap {
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
    // 后面代码省略
}

这里首先我们要看到ThreadLocal中有一个静态类叫做ThreadLocalMap,它里面有一个静态类Entry(可以类比Map中的entry,保存实际的key和value,),ThreadLocalMap其实就是保存了ThreadLocal调用set方法设置的value,key就是ThreadLocal。
总结一下就是当我们使用ThreadLocal的set方法时,ThreadLocal为key,保存的泛型对象为value,存到了ThreadLocal的内部类ThreadLocalMap中,然后ThreaLocalMap的键值对实际上是放在静态类Entry里面。这里稍微提一句
Entry是继承了WeakReference弱引用,key是被弱引用的构造函数给创建的,value是强引用。(java的四种引用可以参考我之前写的文章Java四种引用简介)

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

get方法可以看出,会先获取当前线程,然后获取ThreadLocalMap,然后把值从ThreadLocalMap中取出来,如果没有ThreadLocalMap就去调用setInitialValue()设置完初始值,并返回。
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);
  }

set方法根据当前的线程从ThreadLocalMap中取得,没有map就创建一个。thread中的 ThreadLocal.ThreadLocalMap threadLocals变量会指向刚才创建的ThreadLocalMap。

其他用法

1.初始化值
这样看下来,应该对ThreadLocal的实现和原理有了一个大概的认识。这里提一句,ThreadLocal直接new出来,然后去get的值是null。某些情况下所有的线程都需要有一个初始化的值,这时候可以重写 protected T initialValue()方法,如下:

   private static ThreadLocal<String> threadLocal2 = new ThreadLocal<String>() {
       @Override
       protected String initialValue() {
           return "override initialValue的初始值";
       }
   };

或者jdk1.8可以使用ThreadLocal.withInitial初始化

private static ThreadLocal<String> threadLocal1 = ThreadLocal.withInitial(() -> "withInitial的初始值");

这样所有的线程使用ThreadLocal的get都会是相同的一个初始化值。

2.多个线程有可以相同的值
可以使用InheritableThreadLocal,父线程中设置的值,子线程中可以访问到。这个用法和上面差不多就不举例子了,有兴趣的可以自己研究下~

坑点:

1.上面看ThreadLocalMap的时候,咱们知道key是弱引用,gc的时候key会被回收,但是value和ThreadLocalMap的引用不会被回收。如果这种情况的Thread很多,而且一直没有执行完,就可能会出现内存泄漏。
2.在使用线程池的时候,当使用ThreadLocal调用set方法后,然后没有调用remove的话,因为线程池的线程是复用的,如果同一个线程再去调用get方法可能拿到的值并不是当时set进去的,导致程序数据异常之类的。尽量用完值后就remove掉。

总结:

1.ThreadLocal在同一个线程传值,或者只跟线程相关的场景使用
2.初始化ThreadLocal的值可以用ThreadLocal.withInitial,或重写initialValue()
3.父子线程共享相同的值使用InheritableThreadLocal
4.不管是正常使用还是线程池使用ThreadLocal都一定要使用完就remove,否则会内存泄漏或者数据出错

相关文章
|
Java Shell Linux
Android——编译(二):android.mk的相关知识
Android——编译(二):android.mk的相关知识
1205 0
|
8月前
|
存储 供应链 安全
智慧医疗·区域云HIS源码一体化协同平台 (SaaS版)
专为集团医院、医共体打造的云原生医疗SaaS平台,基于“中心协同、扁平管理”理念,实现全域资源集成与数据共享。覆盖临床、患者服务、供应链及运营监控四大中心,支持五级EMR、医保实时结算、智能预警与多维BI,开箱即用。
|
9月前
|
人工智能 资源调度 供应链
智能体领航员:2026 开启“智能体互联网 (IoA)”下的全自动商业博弈
2026年,智能体互联网(IoA)爆发,智能体从“替代人工”迈向“自主交易”。通过语义化握手、自主谈判与分布式协议,实现跨域意图拦截与资源套利。领航员将升级为规则制定者,掌控价值对齐与安全边界,开启全自动商业新纪元。
617 7
|
11月前
|
人工智能 算法
AI驱动的销售智能体:重塑企业销售全流程新范式,开辟新增长
在流量成本攀升、市场竞争加剧的当下,以AI Agent为核心引擎的销售智能体,正凭借大模型的强大能力,为企业销售全链路赋能,成为降本增效与创新突破的关键抓手。
|
JSON 供应链 监控
批量获取1688商品数据,精准筛选与分页查询
1688商品列表API(alibaba.product.list.get)支持按关键词、类目、价格等条件批量获取商品信息,适用于B2B场景下的商品管理与竞品分析。返回JSON格式数据,含商品ID、标题、价格、库存等核心字段,支持分页查询、动态筛选与排序,实时性强,字段可定制。
|
监控 API 开发者
Sentinel:微服务的全能守护
Sentinel 是阿里巴巴开源的一款轻量级流量控制和熔断降级框架。它通过设置流量控制、熔断降级和系统保护规则,确保微服务在高并发场景下稳定运行。Sentinel 提供丰富的功能、实时监控和灵活的集成方式,适用于各种分布式系统。
2152 0
|
安全 数据库 数据安全/隐私保护
安全策略之授权强制访问控制(MAC)
【8月更文挑战第14天】
1839 2
|
人工智能 运维 监控
智能化运维:AI在IT基础架构管理中的应用
【6月更文挑战第8天】本文将探讨人工智能(AI)如何革新传统的IT运维领域,实现智能化的故障预测、自动化的修复流程以及高效的资源分配。我们将通过实例分析AI技术如何优化数据中心的能源使用,提升网络性能监控的准确性,并降低系统维护成本。
586 2
|
缓存 负载均衡 应用服务中间件
Nginx如何反向代理后端服务?
【7月更文挑战第7天】
993 1
Nginx如何反向代理后端服务?