1.请解释ThreadLocal是什么,以及它的主要用途是什么?
2.ThreadLocal的内部机制是怎样的?请解释一下ThreadLocalMap和Entry。
3.使用ThreadLocal是否会导致内存泄漏?如果是,如何避免?
4.在使用线程池时,ThreadLocal可能会出现什么问题?如何解决?
5.能否解释一下 TransmittableThreadLocal 与ThreadLocal的区别和联系?
6.在父子线程间如何共享数据?ThreadLocal能实现吗?如果不能,那应如何实现?

最新《尼恩 架构笔记》《尼恩高并发三部曲》《尼恩Java面试宝典》的PDF,请关注本公众号【技术自由圈】获取,回复:领电子书
-
第一作者 Moen (负责写初稿 ) -
第二作者 尼恩 (40岁老架构师, 负责提升此文的 技术高度,让大家有一种 俯视 技术的感觉)
本文目录
- 尼恩说在前面
-什么是ThreadLocal(TL)?
-ThreadLocal的基本使用
-ThreadLocal的成员方法
-ThreadLocal的作用和优劣势
-TL作用
-TL优势
-TL劣势
-ThreadLocal的使用场景
-场景1:使用ThreadLocal进行线程隔离
-场景2:使用ThreadLocal进行跨函数数据传递
-场景3:ThreadLocal在Java框架中的应用
-ThreadLocal综合使用案例
-ThreadLocal 使用总结
-ThreadLocal的实现原理
-ThreadLocal内部结构演进
-ThreadLocalMap对象和Entry是什么
-ThreadLocal的结构模型
-ThreadLocal源码分析
-set(T value)方法
-get( )方法
-remove( )方法
-initialValue( ) 方法
-ThreadLocalMap源码分析
-ThreadLocalMap的主要成员变量
-Entry的Key需要使用弱引用
-ThreadLocal造成内存泄露的问题
-ThreadLocal是怎么造成内存泄露的呢?
-ThreadLocal 有两个引用链
-情况1:key的内存泄漏
-情况2:value的内存泄漏
-情况1的解决方案:使用弱引用,解决key的内存泄露
-情况2的解决方案:清理策略解决value内存泄露
-源码:value的 探测式清理 :
-源码:value的启发式清理:
-业务主动清理:手动清除解决内存泄露
-ThreadLocal与内存泄露:防范与诊断
-使用ThreadLocal的性能问题和优化措施
-ThreadLocal的性能开销
-编程规范:推荐使用 static final 修饰ThreadLocal对象
-TheadLocal对象实例存在强引用,会导致三个彻底失效:
-ThreadLocal升级版1:InheritableThreadLocal 可继承本地变量
-什么是可继承本地变量InheritableThreadLocal(ITL)?
-InheritableThreadLocal的基本使用
-InheritableThreaLocal的原理分析
-InheritableThreaLocal所带来的问题
-ThreadLocal升级版2:TransmittableThreadLocal 可透传本地变量
-什么是TransmittableThreadLocal(TTL)?
-TTL 使用场景
-TransmittableThreadLocal的原理分析
-ThreadLocal、InheritableThreaLocal与TransmittableThreadLocal的比较
-ThreadLocal和synchronized之间的比较
-FastThreadLocal (FTL)高性能本地变量的实现原理
-什么是FastThreadLocal (FTL)?
-FastThreadLocal 如何使用
-FastThreadLocal 的优势
-FastThreadLocal 为什么快
-FastThreadLocal 源码分析
-FastThreadLocal 的回收机制
-FastThreadLocal 在Netty中的应用
-和ThreadLocal相比, FastThreadLocal 的优势:
-说在最后:有问题找老架构取经
什么是ThreadLocal(TL)?
线程本地变量” ,或者“线程局部变量” 。线程本地”,实质上ThreadLocal代表的是线程本地变量,可能将其命名为ThreadLocalVariable会更加容易让人理解。This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. 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). 简单翻译如下: 此类提供 线程局部变量。这些变量与其正常对应变量的不同之处在于,每个访问一个变量(通过其get或set方法)的线程都有自己的、独立初始化的变量副本。ThreadLocal实例通常是类中的私有静态字段,这些字段希望将状态与线程(例如,用户ID或事务ID)相关联。
-
ThreadLocal 提供了一种访问某个变量的特殊方式:访问到的变量属于当前线程,即保证每个线程的变量不一样,而同一个线程在任何地方拿到的变量都是当前这个线程私有的,这就是所谓的线程隔离。 -
如果要使用 ThreadLocal,通常定义为 private static类型,根据编程范式最好是定义为private static final类型。
ThreadLocal的基本使用
独立的、自己的本地值。线程本地变量” 可以看成专属于线程的变量,不受其他线程干扰,保存着线程的专属数据。一个ThreadLocal实例可以形象地理解为一个Map(早期版本的ThreadLocal是这样设计的)。当工作线程Thread实例向本地变量保持某个值时,会以“Key-Value对”的形式保存在ThreadLocal内部的Map中,其中Key为线程Thread实例,Value为待保存的值。当工作线程Thread实例从ThreadLocal本地变量取值时,会以Thread实例为Key,获取其绑定的Value。

| 方 法 | 说 明 |
|
|
|
|
|
|
|
|
|
package com.crazymakercircle.mutithread.basic.threadlocal;...省略importpublic class ThreadLocalTest{ @Data static class Foo { //实例总数 static final AtomicInteger AMOUNT = new AtomicInteger(0); //对象的编号 int index = 0; //对象的内容 int bar = 10; //构造器 public Foo() { index = AMOUNT.incrementAndGet(); //总数增加,并且给对象的编号 } @Override public String toString() { return index + "@Foo{bar=" + bar + '}'; } } //定义线程本地变量 private static final ThreadLocal<Foo> LOCAL_FOO = new ThreadLocal<Foo>(); public static void main(String[] args) throws InterruptedException { //获取自定义的混合型线程池 ThreadPoolExecutor threadPool = ThreadUtil.getMixedTargetThreadPool(); //提交5个任务,将会用到5个线程 for (int i = 0; i < 5; i++) { threadPool.execute(new Runnable() { @Override public void run() { //获取“线程本地变量”中当前线程所绑定的值 if (LOCAL_FOO.get() == null) { //设置“线程本地变量”中当前线程所绑定的值 LOCAL_FOO.set(new Foo()); } Print.tco("初始的本地值:" + LOCAL_FOO.get()); //每个线程执行10次 for (int i = 0; i < 10; i++) { Foo foo = LOCAL_FOO.get(); foo.setBar(foo.getBar() + 1); //值增1 sleepMilliSeconds(10); } Print.tco("累加10次之后的本地值:" + LOCAL_FOO.get()); //删除“线程本地变量”中当前线程所绑定的值 LOCAL_FOO.remove(); //这点对于线程池中的线程尤其重要 } }); } }}
[apppool-1-mixed-3]:初始的本地值:3@Foo{bar=10}[apppool-1-mixed-4]:初始的本地值:4@Foo{bar=10}[apppool-1-mixed-5]:初始的本地值:5@Foo{bar=10}[apppool-1-mixed-2]:初始的本地值:1@Foo{bar=10}[apppool-1-mixed-1]:初始的本地值:2@Foo{bar=10}[apppool-1-mixed-1]:累加10次之后的本地值:2@Foo{bar=20}[apppool-1-mixed-3]:累加10次之后的本地值:3@Foo{bar=20}[apppool-1-mixed-5]:累加10次之后的本地值:5@Foo{bar=20}[apppool-1-mixed-2]:累加10次之后的本地值:1@Foo{bar=20}[apppool-1-mixed-4]:累加10次之后的本地值:4@Foo{bar=20}
//获取“线程本地变量”中当前线程所绑定的值 if (LOCAL_FOO.get() == null) { //设置“线程本地变量”中当前线程所绑定的初始值 LOCAL_FOO.set(new Foo()); }
ThreadLocal.withInitial(…)静态工厂方法,ThreadLocal<Foo> LOCAL_FOO = ThreadLocal.withInitial(() -> new Foo());
ThreadLocal的成员方法
set(T value) :设置当前线程在“线程本地变量”实例中绑定的本地值 T get() :获得当前线程在“线程本地变量”实例中绑定的本地值 remove() :移除当前线程在“线程本地变量”实例中绑定的本地值 initialValue( ) : 当“线程本地变量”在当前线程的ThreadLocalMap中尚未绑定值时,该方法用于获取初始值。
ThreadLocal的作用和优劣势
作用
-
线程本地存储:ThreadLocal为每个线程提供各自的变量副本,每个线程都可以读取和修改自己线程的本地变量。这意味着在多线程环境中,不同线程对ThreadLocal变量的操作是独立的,不会互相干扰。 -
简化线程间数据传递:通过使用ThreadLocal,可以将某些需要在多线程之间共享但又需要避免竞态条件的数据封装起来,每个线程访问的都是自己的数据副本,从而简化了线程间数据传递的复杂性。 -
管理线程特定资源:ThreadLocal常用于存储线程上下文信息,如用户会话信息、事务信息等,这些信息通常与特定线程关联,不需要在多个线程之间共享。
优势
-
线程安全:由于每个线程操作的是自己的变量副本,因此避免了多线程访问共享变量时可能出现的竞态条件和数据不一致问题,从而保证了线程安全。 -
简化编程模型:通过使用ThreadLocal,开发者可以更加专注于业务逻辑的实现,而不需要过多关注线程间数据同步和共享的问题,降低了编程复杂度。 -
性能优化:由于避免了线程间数据同步的开销,以及减少了不必要的锁竞争,因此在某些场景下,使用ThreadLocal可以提高系统的并发性能。
劣势
-
内存消耗:ThreadLocal为每个线程创建变量副本,这意味着当线程数量较多时,会占用较多的内存资源。特别是在长时间运行的系统中,如果线程频繁创建和销毁,可能会导致内存泄漏问题。 -
数据共享限制:由于ThreadLocal变量是线程私有的,因此无法直接实现线程间的数据共享。如果需要在线程间传递数据,可能需要借助其他机制(如消息队列、共享内存等)。 -
使用不当可能导致内存泄露问题:如果开发者在使用ThreadLocal时不小心忘记在线程结束后清理变量(例如通过调用 remove()方法),那么这些变量可能会一直存在于内存中,造成内存泄漏。此外,如果多个线程需要访问和修改同一份数据,那么ThreadLocal可能并不适合,因为它提供的是每个线程私有的变量副本。 -
性能开销:在ThreadLocal中ThreadLocalMap 是一种使用线性探测法实现的哈希表,底层采用数组存储数据。ThreadLocal.set()/get() 方法在数据密集时很容易出现 Hash 冲突,hash冲突使用的是线性探测法,需要 O(n) 时间复杂度解决冲突问题,效率较低。
ThreadLocal在简化线程间数据传递、管理线程特定资源和提高线程安全性方面具有优势,但也需要注意其可能带来的内存消耗和数据共享限制等问题。在使用ThreadLocal时,应根据具体的应用场景和需求进行权衡和选择。
ThreadLocal的使用场景
synchronized)解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。 ThreadLocal使用场景,大致可以分为以下两类:-
线程隔离 -
ThreadLocal的主要价值在于线程隔离,ThreadLocal中数据只属于当前线程,其本地值对别的线程是不可见的,在多线程环境下,可以防止自己的变量被其他线程篡改。另外,由于各个线程之间的数据相互隔离,避免同步加锁带来的性能损失,大大提升了并发性的性能。 -
ThreadLocal在线程隔离的最常用案例为:可以每个线程绑定一个用户会话信息、数据库连接、HTTP请求等,这样一个线程的所有调用到的处理函数都可以非常方便地访问这些资源。 -
常见的ThreadLocal使用场景为数据库连接独享、Session数据管理等场景在“线程隔离”场景中使用ThreadLocal的典型案例为:可以每个线程绑定一个数据库连接,是的这个数据库连接为线程所独享,从而避免数据库连接被混用而导致操作异常问题。 -
跨函数传递数据 -
通常用于同一个线程内,跨类、跨方法传递数据时,如果不用ThreadLocal,那么相互之间的数据传递势必要靠返回值和参数,这样无形之中增加了这些类或者方法之间的耦合度。、
场景1:使用ThreadLocal进行线程隔离
private static final ThreadLocal threadSession = new ThreadLocal(); public static Session getSession() throws InfrastructureException { Session s = (Session) threadSession.get(); try { if (s == null) { s = getSessionFactory().openSession(); threadSession.set(s); } } catch (HibernateException ex) { throw new InfrastructureException(ex); } return s; }
Hibernate对数据库连接进行了封装,一个Session 代表一个数据库连接。通过以上代码可以看到,Session关闭,从而节省数据库连接资源。如果Session的使用方式为共享而不是独占,在这种情况下,Session是多线程共享使用的,如果某个线程使用完成之后,直接将Session关闭,其他线程在操作Session就会报错。所以Hibernate通过ThreadLocal非常简单实现了数据库连接的安全使用。场景2:使用ThreadLocal进行跨函数数据传递
跨函数数据传递”应用场景的典型有很多:
用来传递请求过程中的用户ID。 用来传递请求过程中的用户会话(Session)。 用来传递HTTP的用户请求实例HttpRequest。 其他需要在函数之间频繁传递的数据。
Crazy-SpringCloud工程,通过ThreadLocal在函数之间传递用户信息、会话信息等,并且封装成了一个独立的SessionHolder类,具体的代码如下:package com.crazymaker.springcloud.common.context;...省略importpublic class SessionHolder{ // session id 线程本地变量 private static final ThreadLocal<String> sidLocal = new ThreadLocal<>("sidLocal"); // 用户信息 线程本地变量 private static final ThreadLocal<UserDTO> sessionUserLocal = new ThreadLocal<>("sessionUserLocal"); // session 线程本地变量 private static final ThreadLocal<HttpSession> sessionLocal = new ThreadLocal<>("sessionLocal");...省略其他 /** *保存session在线程本地变量中 */ public static void setSession(HttpSession session) { sessionLocal.set(session); } /** * 取得绑定在线程本地变量中的session */ public static HttpSession getSession() { HttpSession session = sessionLocal.get(); Assert.notNull(session, "session 未设置"); return session; } ...省略其他 }
场景3:ThreadLocal在Java框架中的应用
-
Spring -
在Spring框架中,ThreadLocal用于存储数据库连接等线程特定的资源。由于数据库连接是线程不安全的,因此每个线程都需要有自己的连接副本。Spring通过ThreadLocal将数据库连接与当前线程关联起来,从而避免了多线程环境下的数据竞争和不一致问题。 -
在Spring的事务管理中,ThreadLocal也扮演着重要角色。它确保了每个线程都有自己的事务上下文,包括事务状态、回滚点等信息,从而实现了事务的隔离性。 -
MyBatis -
MyBatis是一个优秀的持久层框架,它支持定制化SQL、存储过程以及高级映射。在MyBatis中,ThreadLocal可以用来存储SqlSession对象。由于SqlSession不是线程安全的,因此每个线程都应该拥有自己独立的SqlSession实例。通过ThreadLocal,MyBatis可以方便地实现SqlSession的线程局部存储,确保每个线程都能正确地执行SQL操作。 -
分布式系统 -
在分布式系统中,ThreadLocal可以用来传递全局ID和分支ID等关键信息。这些ID对于分布式事务的追踪和诊断至关重要。通过将这些ID存储在ThreadLocal中,可以确保它们在整个请求处理过程中都能被正确传递和使用。 -
日志框架 -
一些日志框架也利用ThreadLocal来存储与当前线程相关的日志上下文信息,如用户ID、操作类型等。这样,在记录日志时,可以方便地获取这些信息,并将其添加到日志条目中,从而方便后续的日志分析和排查问题。 -
RPC -
在远程过程调用(RPC)框架中,ThreadLocal用于存储和传递与当前调用相关的上下文信息。这些上下文信息可能包括调用者的身份、调用的参数、超时设置等。通过将这些信息存储在ThreadLocal中,可以确保它们在RPC调用过程中能够被正确地传递和使用。 -
Hibernate -
SessionContext: 用于存储当前线程的Hibernate会话相关数据,如当前会话、持久化上下文等。 -
TransactionManager: 管理事务状态,每个线程可以有独立的事务状态,如当前是否在事务中。 -
Tomcat -
ThreadLocal变量: 用于跟踪每个请求的会话信息、用户认证数据等,确保这些数据不会在请求之间共享。 -
Kafka -
Producer and Consumer Threads: 在消息生产和消费过程中,使用ThreadLocal来存储线程特定的配置和状态信息。
ThreadLocal综合使用案例
-
尽量使用private static final修饰ThreadLocal实例。使用 private 与final 修饰符,主要是尽可能不让他人修改、变更ThreadLocal变量的引用; 使用static 修饰符主要为了确保ThreadLocal实例的全局唯一。 -
ThreadLocal使用完成之后务必调用remove方法。这是简单、有效地避免ThreadLocal引发内存泄漏的方法。 下面用一个综合案例演示一下ThreadLocal的使用。此案例的功能为:记录执行过程中所调用的函数的执行耗时。比如在实际Web开发过程中,一次客户端请求往往会涉及到DB、缓存、RPC等多个耗时调用,一旦出现性能问题,就需要记录一下各个点耗时的时间,从而判断性能的瓶颈所在。 下面的代码定义了三个方法 serviceMethod、daoMethod、rpcMethod,用于模拟实际的DB、RPC等耗时调用,具体的代码如下:
package com.crazymakercircle.mutithread.basic.threadlocal;...省略importpublic class ThreadLocalTest2{ /** * 模拟业务方法 */ public void serviceMethod() { //睡眠500ms,模拟执行耗时 sleepMilliSeconds(500); //记录从开始调用到当前这个点( "point-1")的耗时 SpeedLog.logPoint("point-1 service"); //调用DAO方法:模拟dao业务方法 daoMethod(); //调用RPC方法:模拟RPC远程业务方法 rpcMethod(); } /** * 模拟dao业务方法 */ public void daoMethod() { //睡眠400ms,模拟执行耗时 sleepMilliSeconds(400); //记录上一个点("point-1")这里("point-2")的耗时 SpeedLog.logPoint("point-2 dao"); } /** * 模拟RPC远程业务方法 */ public void rpcMethod() { //睡眠400ms,模拟执行耗时 sleepMilliSeconds(600); //记录上一个点("point-2")这里("point-3")的耗时 SpeedLog.logPoint("point-3 rpc"); } ...省略不相干代码}
package com.crazymakercircle.mutithread.basic.threadlocal;...省略importpublic class SpeedLog{ /** * 记录调用耗时的本地Map变量 */private static final ThreadLocal<Map<String, Long>> TIME_RECORD_LOCAL =ThreadLocal.withInitial(SpeedLog::initialStartTime); /** * 记录调用耗时的本地Map变量的初始化方法 */ public static Map<String, Long> initialStartTime() { Map<String, Long> map = new HashMap<>(); map.put("start", System.currentTimeMillis()); map.put("last", System.currentTimeMillis()); return map; } /** * 开始耗时记录 */ public static final void beginSpeedLog() { Print.fo("开始耗时记录"); TIME_RECORD_LOCAL.get(); } /** * 结束耗时记录 */ public static final void endSpeedLog() { TIME_RECORD_LOCAL.remove(); Print.fo("结束耗时记录"); } /** * 耗时埋点 */ public static final void logPoint(String point) { //获取上一次的时间 Long last = TIME_RECORD_LOCAL.get().get("last"); //计算上一次埋点到当前埋点的耗时 Long cost = System.currentTimeMillis() - last; //保存上一次埋点到当前埋点的耗时 TIME_RECORD_LOCAL.get().put(point + " cost:", cost); //保存当前时间,供下一次埋点使用 TIME_RECORD_LOCAL.get().put("last", System.currentTimeMillis()); } ...省略不相干代码}
package com.crazymakercircle.mutithread.basic.threadlocal;...省略importpublic class ThreadLocalTest2{ /** * 测试用例:线程方法调用的耗时 */ @org.junit.Test public void testSpeedLog() throws InterruptedException { Runnable runnable = () -> { //开始耗时记录,保存当前时间 SpeedLog.beginSpeedLog(); //调用模拟业务方法 serviceMethod(); //打印耗时 SpeedLog.printCost(); //结束耗时记录 SpeedLog.endSpeedLog(); }; new Thread(runnable).start(); sleepSeconds(10);//等待10s看结果 } ...省略不相干代码}
[SpeedLog.beginSpeedLog]:开始耗时记录[SpeedLog.printCost]:start =>1600347227334[SpeedLog.printCost]:point-1 service cost: =>500[SpeedLog.printCost]:point-2 dao cost: =>401[SpeedLog.printCost]:point-3 rpc cost: =>600[SpeedLog.printCost]:last =>1600347228835[SpeedLog.endSpeedLog]:结束耗时记录
private static final的形式,使得外部不能直接访问,外部能访问的是将ThreadLocal变量封装之后的接口函数如beginSpeedLog( )、logPoint(String point)、endSpeedLog( )等等。 总之,使用ThreadLocal能实现每个线程都有一份变量的本地值,其原因是由于每个线程都有自己独立的ThreadLocalMap空间,本质上属于以空间换时间的设计思路,该设计思路属于了另一种意义的 “无锁编程”。ThreadLocal 使用总结
ThreadLocal的实现原理
空间换时间的做法,在每个 Thread 里面维护了一个以开放定址法实现的ThreadLocal.ThreadLocalMap,把数据进行隔离,数据不共享,自然就没有线程安全方面的问题了。ThreadLocal内部结构演进
ThreadLocal,每一个ThreadLocal实例,拥有一个Map实例。
Thread(线程)实例,每一个Thread实例,拥有一个Map实例。另外,Map结构的Key值也发生了变化:新的Key为ThreadLocal实例。
ThreadLocalMap),因为如我们给一个Thread创建多个Threadlocal实列,然乎放置本地数据,那么当前线程的ThreadLocalMap中就会有多个“Key-Value对”,其中ThreadLocal实例为key,本地数据为Value。-
拥有者发生了变化:新版本的ThreadLocalMap拥有者为 Thread,早期版本的ThreadLocalMap拥有者为ThreadLocal。 -
Key发生了变化:新版本的Key为 ThreadLocal实例,早期版本的Key为Thread实例。
新版本的主要优势为:-
早期版本的“Key-Value对”数量与线程个数强关联,如果线程数量多,则ThreadLocalMap存储“Key-Value对”数量也多。 -
新版本的ThreadLocalMap的Key 为ThreadLocal实例,多线程情况下ThreadLocal实例比线程数少。
-
早期版本ThreadLocalMap的拥有者为ThreadLocal,在Thread(线程)实例销毁后,ThreadLocalMap还是存在的; -
新版本的ThreadLocalMap的拥有者为Thread,现在当Thread实例销毁后,ThreadLocalMap也会随之销毁,在一定程度上能减少内存的消耗。
ThreadLocalMap对象和Entry是什么
ThreadLocalMap展开的,而ThreadLocalMap是ThreadLocal的一个静态内部类,其实现了一套简单的Map结构(比HashMap简单)。
ThreadLocal的结构模型

ThreadLocal源码分析
set(T value)方法、get( )方法、remove( )方法、initialValue( )方法。set(T value)方法
set(T value)方法用于设置“线程本地变量”在当前线程的ThreadLocalMap中对应的值,相当于设置线程本地值,其核心源码如下:public void set(T value) { //获取当前线程对象 Thread t = Thread.currentThread(); //获取当前线程的ThreadLocalMap 成员 ThreadLocalMap map = getMap(t); //判断map是否存在 if (map != null) { //value被绑定到threadLocal实例 map.set(this, value); } else { // 如果当前线程没有ThreadLocalMap成员实例 // 创建一个ThreadLocalMap实例,然后,作为成员关联到t(thread实例) createMap(t, value); } } // 获取线程t的ThreadLocalMap成员 ThreadLocalMap getMap(Thread t) { return t.threadLocals; } // 线程t创建一个ThreadLocalMap成员 //并为新的Map成员设置第一个Key-Value对,Key为当前的ThreadLocal实例 void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
set(T value)方法的执行流程,大致如下:
获得当前线程,然后获得当前线程的ThreadLocalMap成员,暂存于map变量。 如果map不为空,则将Value设置到map中,当前的Threadlocal作为key。 如果map为空,给该线程创建map,然后设置第一个“Key-Value对”,Key为当前的ThreadLocal实例,Value为set方法的参数value值。
get( )方法
get( )方法用于获取“线程本地变量”在当前线程的ThreadLocalMap中对应的值,相当于获取线程本地值,其核心源码如下:public T get() { //获得当前线程对象 Thread t = Thread.currentThread(); //获得线程对象的ThreadLocalMap 内部成员 ThreadLocalMap map = getMap(t); // 如果当前线程的内部map成员存在 if (map != null) { // 以当前threadlocal为Key,尝试获得条目 ThreadLocalMap.Entry e = map.getEntry(this); // 条目存在 if (e != null) { T result = (T)e.value; return result; } } // 如果当前线程对应map不存在 //或者map存在,但是当前threadlocal实例没有对应的Key-Value,返回初始值 return setInitialValue();}// 设置threadlocal关联的初始值并返回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;}
T get()方法的执行流程,大致如下:
先尝试获得当前线程,然后获得当前线程的ThreadLocalMap成员,暂存于map变量。 如果获得的map不为空,以当前threadlocal实例为Key尝试获得map中的Entry(条目)。 如果Entry条目不为空,返回Entry中的Value。 如果Entry为空,则通过调用initialValue初始化钩子函数获取“ThreadLocal”初始值,并设置在map中。如果map不存在,还会给当前线程创建新ThreadLocalMap成员,并绑定第一个“Key-Value对”。
remove( )方法
remove()方法用于在当前线程的ThreadLocalMap中,移除“线程本地变量”所对应的值,其核心源码如下:public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
initialValue( ) 方法
ThreadLocalMap中尚未绑定值时,initialValue( )方法用于获取初始值。其源码如下:protected T initialValue() { return null; }
ThreadLocal.withInitial(…)静态工厂方法,方便大家在定义ThreadLocal实例时设置初始值回调函数。ThreadLocal<Foo> LOCAL_FOO = ThreadLocal.withInitial(() -> new Foo());
ThreadLocal.withInitial(…)静态工厂方法,以及其内部子类SuppliedThreadLocal的源码如下://ThreadLocal工厂方法,可以设置本地变量初始值钩子函数 public static <S> ThreadLocal<S> withInitial( Supplier<? extends S> supplier) { return new SuppliedThreadLocal<>(supplier); } //内部静态子类 //继承了ThreadLocal,重写了initialValue()方法,返回钩子函数的值作为初始值 static final class SuppliedThreadLocal<T> extends ThreadLocal<T> { //保存钩子函数 private final Supplier<? extends T> supplier; //传入钩子函数 SuppliedThreadLocal(Supplier<? extends T> supplier) { this.supplier = Objects.requireNonNull(supplier); } @Override protected T initialValue() { return supplier.get(); //返回钩子函数的值作为初始值 } }
ThreadLocalMap源码分析
ThreadLocalMap展开的,而ThreadLocalMap是ThreadLocal的一个静态内部类,其实现了一套简单的Map结构(比HashMap简单)。ThreadLocalMap的主要成员变量
ThreadLocalMap的成员变量与HashMap的成员变量非常类似,其内部的主要成员如下所示:public class ThreadLocal<T> { ...省略其他 static class ThreadLocalMap { //Map的条目数组,作为散列表使用 private Entry[] table; //Map的条目初始容量16 private static final int INITIAL_CAPACITY = 16; //Map的条目数量 private int size = 0; //扩容因子 private int threshold; //Map的条目类型,一个静态的内部类 // Entry 继承子WeakReference,Key为ThreadLocal实例 static class Entry extends WeakReference<ThreadLocal<?>> { Object value; //条目的值 Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } ...省略其他 }
get()、set( )、remove()方法都涉及到ThreadLocalMap的方法调用,主要调用了ThreadLocalMap的如下几个函数:
set(ThreadLocal<?> key, Object value) :向Map实例设置“Key-Value对”。 getEntry(ThreadLocal):从Map实例获取Key(ThreadLocal实例)所属的Entry。 remove(ThreadLocal):根据Key(ThreadLocal实例)从Map实例移除所属的Entry。
ThreadLocalMap的set(ThreadLocal<?> key, Object value) 方法的代码以注释的形式做一个简单的分析,具体如下:private void set(ThreadLocal<?> key, Object value) { Entry[] tab = table; int len = tab.length; //根据key的HashCode,找到key在数组上的槽点i int i = key.threadLocalHashCode & (len-1); // 从槽点i开始向后循环搜索,找空余槽点(空余位置)或者找现有槽点 //如果没有现有槽点,则必定有空余槽点,因为没有空间时会扩容 for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); //找到现有槽点:Key值为ThreadLocal实例 if (k == key) { e.value = value; return; } //找到异常槽点:槽点被GC掉,重设Key值和Value值 if (k == null) { replaceStaleEntry(key, value, i); return; } } //没有找到现有的槽点,增加新的Entry tab[i] = new Entry(key, value); //设置ThreadLocal数量 int sz = ++size; //清理Key为null的无效Entry //没有可清理的Entry,并且现有条目数量大于扩容因子值,进行扩容 if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }
Entry的Key需要使用弱引用
Entry用于保存ThreadLocalMap的“Key-Value”条目,但是Entry使用了对Threadlocal实例进行包装之后的弱引用(WeakReference)作为Key,其代码如下:// Entry 继承了WeakReference,并使用WeakReference对Key进行包装static class Entry extends WeakReference<ThreadLocal<?>> { Object value; //值 Entry(ThreadLocal<?> k, Object v) { super(k); //使用WeakReference对Key值进行包装 value = v; }}
public void funcA() { //创建一个线程本地变量 ThreadLocal local = new ThreadLocal<Integer>(); //设置值 local.set(100); //获取值 local.get(); //函数末尾 }
local.set(100)之后,线程tn的ThreadLocalMap成员内部会新建一个Entry实例,其Key 以弱引用包装的方式指向ThreadLocal实例。ThreadLocal实例。强引用,就会导致Key引用指向的ThreadLocal实例、及其Value值都不能被GC回收,这将造成严重的内存泄露,具体如图1-21所示。

图1-21 若Entry的Key为强引用将导致ThreadLocal实例不能回收
仅有弱引用(WeakReference)指向的对象,只能生存到下一次垃圾回收之前。 换句话说,当GC发生时,不管内存够不够,仅有弱引用所指向的对象都会被回收。而拥有强引用指向的对象,则不会被直接回收。
不再用到的内存,没有及时释放,就叫做内存泄漏。 对于持续运行的服务进程,必须及时释放内存,否则内存占用率越来越高,轻则影响系统性能,重则导致进程崩溃。
get、set或remove被调用时,ThreadLocalMap的内部代码会清除这些Key为null的Entry,从而完成相应的内存释放。内存泄漏的前提条件:
线程长时间运行而没有被销毁。 线程池中的Thread实例很容易满足此条件。ThreadLocal引用被设置为null,且后续在同一Thread实例的执行期间,没有发生对其他ThreadLocal实例的get、set或remove操作。
面试难点:ThreadLocal造成内存泄露的问题
不再用到的内存,没有及时释放,就叫做内存泄漏。 对于持续运行的服务进程,必须及时释放内存,否则内存占用率越来越高,轻则影响系统性能,重则导致进程崩溃。
ThreadLocal是怎么造成内存泄露的呢?
-
如果ThreadLocal是null了,也就是要被GC回收了, -
但是此时我们的ThreadLocalMap(thread 的内部属性)生命周期和Thread的一样,它不会回收,这时候就出现了一个现象。
ThreadLocal 有两个引用链
-
一个引用链是栈内存中ThreadLocal引用: -
一个引用链是ThreadLocalMap中的Key对它的引用: 

情况1:key的泄漏

情况2: value的泄漏
如在线程池中被重复使用),但是此时我们的ThreadLocalMap(thread 的内部属性)生命周期和Thread的一样,它不会回收,这时候就出现了一个现象。情况1的解决方案:使用弱引用,解决key的内存泄露
static class ThreadLocalMap { static class Entry extends WeakReference<ThreadLocal<?>> { Object value; Entry(ThreadLocal<?> k, Object v) { super(k); // key为弱引用 value = v; } }}

强引用(Strong Reference):
这是最常见的引用类型。一个对象具有强引用,垃圾收集器就不会回收它,即使系统内存空间不足。 示例: Object obj = new Object();在这里,obj就是new Object()的一个强引用。软引用(Soft Reference):
用来描述一些可能还有用但并非必需的对象。在系统将要发生内存溢出异常前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。 在Java中,软引用是用来实现内存敏感的高速缓存。 示例:使用 java.lang.ref.SoftReference类可以创建软引用。弱引用(Weak Reference):
这里讨论ThreadLocalMap中Entry类的重点。 弱引用也是用来描述非必需对象的,它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收被弱引用关联的对象。 在Java中,弱引用是用来描述那些非关键的数据,在Java里用 java.lang.ref.WeakReference类来表示。示例:使用 java.lang.ref.WeakReference类可以创建弱引用。虚引用(Phantom Reference):
一个虚引用关联着的对象,在任何时候都可能被垃圾收集器回收,它不能单独用来获取被引用的对象。虚引用必须和引用队列( ReferenceQueue)联合使用。主要用来跟踪对象被垃圾回收的活动。虚引用对于一般的应用程序来说意义不大,主要使用在能比较精确控制Java垃圾收集器的高级场景中。 示例:使用 java.lang.ref.PhantomReference类可以创建虚引用。
情况2的解决方案:清理策略解决value内存泄露
-
探测式清理(Proactive Cleanup) -
启发式清理(Heuristic Cleanup) 。
源码:value的 探测式清理 :
ThreadLocal的get()、set()或remove()方法时,会探测式的去触发对 ThreadLocalMap 的清理。private int expungeStaleEntry(int staleSlot) { Entry[] tab = table; int len = tab.length; // expunge entry at staleSlot tab[staleSlot].value = null; tab[staleSlot] = null; size--; // Rehash until we encounter null Entry e; int i; for (i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) { ThreadLocal<?> k = e.get(); if (k == null) { // 将k=null的entry置为null e.value = null; tab[i] = null; size--; } else { // k不为null,则rehash从新分配配置 int h = k.threadLocalHashCode & (len - 1); if (h != i) { tab[i] = null; // Unlike Knuth 6.4 Algorithm R, we must scan until // null because multiple entries could have been stale. while (tab[h] != null) // 重新分配后的位置上有元素则往后顺延。 h = nextIndex(h, len); tab[h] = e; } } } return i; }
源码:value的启发式清理:
ThreadLocalMap的 set() 方法中,有一个阈值(默认为 ThreadLocalMap.Entry 数组长度的 1/4)。从当前节点开始,进行do-while循环检查清理过期key,结束条件是连续n次未发现过期key就跳出循环,n是经过位运算计算得出的,可以简单理解为数组长度的2的多少次幂次。
private boolean cleanSomeSlots(int i, int n) { boolean removed = false; Entry[] tab = table; int len = tab.length; do { i = nextIndex(i, len); Entry e = tab[i]; if (e != null && e.get() == null) { n = len; removed = true; // 移除 i = expungeStaleEntry(i); } } while ( (n >>>= 1) != 0); return removed; }
业务主动清理:手动清除解决内存泄露
remove()方法来清除它:remove()方法,以确保不再需要的值能够被及时回收,key和value 都同时清理,一锅端。
-
比如Netty的内存池和对象池(那个超级难,很多人穷其一生都搞不懂), -
比如DDD的建模和落地, -
比如Caffeine的底层架构, -
比如高性能葵花宝典
-
等等等等。
使用ThreadLocal的性能问题和优化措施
ThreadLocal的性能开销
ThreadLocalMap的维护。 ThreadLocal变量的创建和销毁。
编程规范:推荐使用 static final 修饰ThreadLocal对象
static修饰ThreadLocal 就会节约内存空间。final进行加强修饰,以防止其在使用过程中发生动态变更。参考的实例如下://推荐使用static final线程本地变量 private static final ThreadLocal<Foo> LOCAL_FOO = new ThreadLocal<Foo>();
private修饰呢?-
为啥内存泄露又出来了? -
上面不是解决了吗? -
呜呜呜 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

TheadLocal对象实例存在强引用,会导致三个彻底失效:
-
探测式清理(Proactive Cleanup) 彻底失效 -
启发式清理(Heuristic Cleanup)彻底失效 。 
remove()进行手动释放。afterExecute方法(任务执行完成之后的钩子方法),在任务执行完成之后,调用TheadLocal实例的remove()方法对其手动释放,从而实现的其线程内部的Entry得到释放,参考的代码如下://线程本地变量,用于记录线程异步任务的开始执行时间 private static final ThreadLocal<Long> START_TIME= new ThreadLocal<>(); ExecutorService pool = new ThreadPoolExecutor(2, 4, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(2)) { ...省略其他 //异步任务执行完成之后的钩子方法 @Override protected void afterExecute(Runnable target, Throwable t) { ...省略其他 //清空TheadLocal实例的本地值 startTime.remove(); } };
ThreadLocal升级版1:InheritableThreadLocal 可继承本地变量
什么是可继承本地变量InheritableThreadLocal(ITL)?
文档太长,超过了 平台限制........ 这部分详细内容略,请参见PDF 《ThreadLocal 学习圣经:一次穿透TL、ITL、TTL、FTL》
InheritableThreadLocal的基本使用
文档太长,超过了 平台限制........ 这部分详细内容略,请参见PDF 《ThreadLocal 学习圣经:一次穿透TL、ITL、TTL、FTL》
InheritableThreaLocal的原理分析
文档太长,超过了 平台限制........ 这部分详细内容略,请参见PDF 《ThreadLocal 学习圣经:一次穿透TL、ITL、TTL、FTL》
InheritableThreaLocal所带来的问题
文档太长,超过了 平台限制........ 这部分详细内容略,请参见PDF 《ThreadLocal 学习圣经:一次穿透TL、ITL、TTL、FTL》
ThreadLocal升级版2:TransmittableThreadLocal 可透传本地变量
文档太长,超过了 平台限制........ 这部分详细内容略,请参见PDF 《ThreadLocal 学习圣经:一次穿透TL、ITL、TTL、FTL》
什么是TransmittableThreadLocal(TTL)?
文档太长,超过了 平台限制........ 这部分详细内容略,请参见PDF 《ThreadLocal 学习圣经:一次穿透TL、ITL、TTL、FTL》
TTL 使用场景
文档太长,超过了 平台限制........ 这部分详细内容略,请参见PDF 《ThreadLocal 学习圣经:一次穿透TL、ITL、TTL、FTL》
TransmittableThreadLocal的原理分析
文档太长,超过了 平台限制........ 这部分详细内容略,请参见PDF 《ThreadLocal 学习圣经:一次穿透TL、ITL、TTL、FTL》
ThreadLocal、InheritableThreaLocal与TransmittableThreadLocal的比较
-
ThreadLocal -
使用场景:适用于需要在线程内部存储和获取数据,且不希望与其他线程共享数据的场景。 -
变量存储:提供了一种在线程内部存储变量的机制,每个线程可以独立地改变自己的副本,而不会影响到其他线程。 -
线程隔离:通过为每个线程创建变量副本,ThreadLocal实现了线程间的数据隔离,提高了多线程程序的性能。 -
定义:ThreadLocal是Java中一个非常重要的线程技术,它为每个线程提供了它自己的变量副本,使得线程间无法相互访问对方的变量,从而避免了线程间的竞争和数据泄露问题。 -
特性: -
变量连续性:当线程切换时,ThreadLocal可以保持变量的连续性。 -
InheritableThreadLocal -
线程继承:允许父线程中的InheritableThreadLocal变量的值被子线程继承。当创建一个新的线程时,这个新线程可以访问其父线程中InheritableThreadLocal变量的值。 -
定义:InheritableThreadLocal是ThreadLocal的一个子类,它包含了ThreadLocal的所有功能,并扩展了ThreadLocal的功能。 -
特性: -
使用场景:适用于需要在父线程和子线程之间传递数据的场景,如线程池中的任务传递等。 -
TransmittableThreadLocal 总结起来如下: -
跨线程传递:能够在多线程传递中保持变量的传递性,确保在父线程和子线程之间正确传递ThreadLocal变量。 -
copy 方法用于定制任务提交给线程池时的 ThreadLocal 值传递到任务执行时的拷贝行为,缺省传递的是引用。注意:如果跨线程传递了对象引用因为不再有线程封闭,与 InheritableThreadLocal.childValue 一样,使用者/业务逻辑要注意传递对象的线程安全。 -
protected 的 beforeExecute/afterExecute 方法执行任务(Runnable/Callable)的前/后的生命周期回调。 -
定义:TransmittableThreadLocal是阿里巴巴开源的一个框架,用于解决在使用线程池等场景下,ThreadLocal变量无法跨线程传递的问题。 -
特性: -
使用场景:适用于需要在线程池等场景下跨线程传递ThreadLocal变量的场景。
ThreadLocal适用于线程内部的数据存储和访问,确保数据在线程间的隔离。 InheritableThreadLocal适用于需要在父线程和子线程间传递数据的场景,实现数据的继承。 TransmittableThreadLocal则是为了解决在使用线程池等场景下,ThreadLocal变量无法跨线程传递的问题,实现数据的跨线程传递。
ThreadLocal和synchronized之间的比较
-
核心思想 -
ThreadLocal:其核心思想是以空间换时间。它为每个线程提供了一个独立的变量副本,使得每个线程都可以访问和修改自己的变量副本,而不会影响到其他线程。由于每个线程操作的是自己的变量副本,因此多个线程可以同时访问该变量,且相互之间不会产生影响。这种机制主要用于保存线程私有数据、提高性能、管理线程特定的资源等场景。 -
synchronized:其核心思想是以时间换空间。它确保同一时刻只有一个线程能够执行被synchronized修饰的代码块或方法,其他线程必须等待锁的释放。多个线程访问的是同一个变量,当多个线程同时访问该变量时,需要抢占锁,并且等待获取锁的线程释放锁,因此会消耗较多的时间。synchronized主要用于保护共享资源,防止竞态条件和数据不一致问题。 -
应用场景 -
ThreadLocal主要用于线程间的数据隔离,常见应用场景包括线程封闭(将对象封闭在单个线程中,避免线程安全问题)、保存线程上下文信息(如在Web开发中存储用户信息和请求参数)、数据库连接管理(确保每个线程获取到自己的数据库连接)以及线程池中的任务隔离等。 -
Synchronized主要用于线程间的数据共享,常用于保护共享资源,如共享数据或对象,确保同一时间只有一个线程访问这些资源。此外,它还可以用于保护需要原子性执行的代码块,防止多线程并发执行导致的问题。 -
性能和资源消耗 综上所述: -
ThreadLocal为每个线程创建变量副本,因此会消耗较多的内存。但由于线程间互不干扰,所以并发性能较高。 -
synchronized则通过锁机制来控制线程对共享资源的访问,虽然节省了内存空间,但在多线程环境下可能会因为锁竞争而降低性能。
ThreadLocal和synchronized在解决多线程访问相同变量的冲突问题上各有其特点和适用场景。选择使用哪种机制应根据具体的业务需求、性能要求和资源限制来决定。
FastThreadLocal (FTL)的实现原理
ThreadLocal是一个常用的工具类,它允许我们创建线程局部变量。这意味着每个线程都可以独立地改变自己的副本,而不会影响其他线程所持有的数据。ThreadLocal在高并发环境下存在一些问题:-
内存占用:每个 ThreadLocal变量都会在每个线程中持有一个独立的副本,这可能导致大量的内存占用。 -
性能开销:创建和销毁这些线程局部变量会带来额外的性能开销。
FastThreadLocal就是为了解决这些问题而诞生的。什么是FastThreadLocal (FTL)?
文档太长,超过了 平台限制........ 这部分详细内容略,请参见PDF 《ThreadLocal 学习圣经:一次穿透TL、ITL、TTL、FTL》
FastThreadLocal 如何使用
文档太长,超过了 平台限制........ 这部分详细内容略,请参见PDF 《ThreadLocal 学习圣经:一次穿透TL、ITL、TTL、FTL》
FastThreadLocal 的优势
文档太长,超过了 平台限制........ 这部分详细内容略,请参见PDF 《ThreadLocal 学习圣经:一次穿透TL、ITL、TTL、FTL》
FastThreadLocal 为什么快
public class FastThreadLocalThread extends Thread {private InternalThreadLocalMap threadLocalMap;// 省略其他代码}
public final class InternalThreadLocalMap extends UnpaddedInternalThreadLocalMap { private static final InternalLogger logger = InternalLoggerFactory.getInstance(InternalThreadLocalMap.class); private static final ThreadLocal<InternalThreadLocalMap> slowThreadLocalMap = new ThreadLocal<InternalThreadLocalMap>(); private static final AtomicInteger nextIndex = new AtomicInteger(); private static final int DEFAULT_ARRAY_LIST_INITIAL_CAPACITY = 8; private static final int ARRAY_LIST_CAPACITY_EXPAND_THRESHOLD = 1 << 30; // Reference: https://hg.openjdk.java.net/jdk8/jdk8/jdk/file/tip/src/share/classes/java/util/ArrayList.java#l229 private static final int ARRAY_LIST_CAPACITY_MAX_SIZE = Integer.MAX_VALUE - 8; private static final int STRING_BUILDER_INITIAL_SIZE; private static final int STRING_BUILDER_MAX_SIZE; private static final int HANDLER_SHARABLE_CACHE_INITIAL_CAPACITY = 4; private static final int INDEXED_VARIABLE_TABLE_INITIAL_SIZE = 32; public static final Object UNSET = new Object(); /** Used by {@link FastThreadLocal} */ private Object[] indexedVariables; // Core thread-locals private int futureListenerStackDepth; private int localChannelReaderStackDepth; private Map<Class<?>, Boolean> handlerSharableCache; private IntegerHolder counterHashCode; private ThreadLocalRandom random; private Map<Class<?>, TypeParameterMatcher> typeParameterMatcherGetCache; private Map<Class<?>, Map<String, TypeParameterMatcher>> typeParameterMatcherFindCache; // String-related thread-locals private StringBuilder stringBuilder; private Map<Charset, CharsetEncoder> charsetEncoderCache; private Map<Charset, CharsetDecoder> charsetDecoderCache; // ArrayList-related thread-locals private ArrayList<Object> arrayList; private BitSet cleanerFlags; /** @deprecated These padding fields will be removed in the future. */ public long rp1, rp2, rp3, rp4, rp5, rp6, rp7, rp8; static { STRING_BUILDER_INITIAL_SIZE = SystemPropertyUtil.getInt("io.netty.threadLocalMap.stringBuilder.initialSize", 1024); logger.debug("-Dio.netty.threadLocalMap.stringBuilder.initialSize: {}", STRING_BUILDER_INITIAL_SIZE); STRING_BUILDER_MAX_SIZE = SystemPropertyUtil.getInt("io.netty.threadLocalMap.stringBuilder.maxSize", 1024 * 4); logger.debug("-Dio.netty.threadLocalMap.stringBuilder.maxSize: {}", STRING_BUILDER_MAX_SIZE); }// 省略其他代码}
数组的存储方式。全局唯一的索引 index,数组索引 index 的值采用原子类AtomicInteger保证顺序递增,-
每一个本地变量,分配一个 全局唯一的索引 index. -
这个数组索引 index 的值 和本地变量绑定, 通过调用InternalThreadLocalMap.nextVariableIndex() 方法获得。



-
InternalThreadLocalMap中并不是Entry的key-value结构, 而是Object数组 -
索引0位置存放FastThreadLocal的Set集合, 其他索引位置初始化为UNSET, 数据存入的时候更新为具体的Object -
FastThreadLocal中包含一个自增的index表示在InternalThreadLocalMap的数组中的索引位置 -
Set<FastThreadLocal<?>>结构中存放FastThreadLocal的引用, 更容易解决内存泄漏的问题 通过上面 FastThreadLocal 的内部结构图,我们对比下与 ThreadLocal 有哪些区别?
-
FastThreadLocal 使用 Object 数组替代了 Entry 数组, -
Object[0] 存储的是一个Set集合, -
从数组下标 1 开始都是直接存储的 value 数据,不再采用ThreadLocal 的键值对形式进行存储。
FastThreadLocal 源码分析
文档太长,超过了 平台限制........ 这部分详细内容略,请参见PDF 《ThreadLocal 学习圣经:一次穿透TL、ITL、TTL、FTL》
FastThreadLocal 的回收机制
文档太长,超过了 平台限制........ 这部分详细内容略,请参见PDF 《ThreadLocal 学习圣经:一次穿透TL、ITL、TTL、FTL》
FastThreadLocal 在Netty中的应用
文档太长,超过了 平台限制........ 这部分详细内容略,请参见PDF 《ThreadLocal 学习圣经:一次穿透TL、ITL、TTL、FTL》
和ThreadLocal相比, FastThreadLocal 的优势:
文档太长,超过了 平台限制........ 这部分详细内容略,请参见PDF 《ThreadLocal 学习圣经:一次穿透TL、ITL、TTL、FTL》
说在最后:有问题找老架构取经
部分历史案例
实现职业转型,极速上岸

关注职业救助站公众号,获取每天职业干货助您实现职业转型、职业升级、极速上岸---------------------------------
实现架构转型,再无中年危机

关注技术自由圈公众号,获取每天技术千货一起成为牛逼的未来超级架构师
几十篇架构笔记、5000页面试宝典、20个技术圣经请加尼恩个人微信免费拿走
暗号,请在 公众号后台 发送消息:领电子书
如有收获,请点击底部的"在看"和"赞",谢谢
本篇文章来源于微信公众号: 技术自由圈
微信扫描下方的二维码阅读本文




Comments NOTHING