资讯动态

TreadLocal和TreadLocalMap

发布时间:2026/10/2 19:34:32 来源:尧图企业网站定制
文章目录一、ThreadLocal二、ThreadLocal的线程隔离1.set方法源码2.get方法源码三、ThreadLocalMap1.ThreadLocalMap的哈希冲突2.过期Key的探测式清理四、ThreadLocalg内存泄漏问题五、InheritableThreadLocal父子线程数据传递实际应用场景1.数据库连接管理2.用户会话信息存储面试问题一、ThreadLocal在多线程环境下多个线程访问同一个共享变量时容易出现线程安全问题。我们通常用synchronized或Lock来加锁解决但加锁会让线程排队执行降低并发效率。ThreadLocal提供了另一种思路为每个线程创建独立的变量副本每个线程操作自己的那份副本互不干扰。这样既避免了线程安全问题又无需加锁性能更高。set(T value)在当前线程中设置一个值。get()获取当前线程中存储的值。remove()移除当前线程中存储的值防止内存泄漏非常重要 。classThreadLocalDemo{// 声明一个ThreadLocal变量建议用static final修饰privatestaticfinalThreadLocalStringthreadLocalnewThreadLocal();privatestaticfinalThreadLocalStringthreadLocal1newThreadLocal();publicstaticvoidmain(String[]args){// 线程1存入张三newThread(()-{threadLocal.set(张三);threadLocal1.set(aaa);System.out.println(线程1获取threadLocal.get());// 张三System.out.println(线程1获取threadLocal1.get());// aaathreadLocal.remove();// 用完清理},线程1).start();// 线程2存入李四newThread(()-{threadLocal.set(李四);System.out.println(线程2获取threadLocal.get());// 李四threadLocal.remove();},线程2).start();// 主线程没有存过获取为nullSystem.out.println(主线程获取threadLocal.get());// null}}输出结果线程1打印张三线程1的threadLocal1打印aaa线程2打印李四主线程打印null——三个线程互不影响。二、ThreadLocal的线程隔离ThreadLocal线程隔离核心每个线程Thread对象内部都持有一个ThreadLocalMapThread 对象持有 ThreadLocal.ThreadLocalMap 类型的成员变量ThreadLocalMap 内部是一个 Entry[] 数组Entry 继承自 WeakReferenceThreadLocal?key 是对 ThreadLocal 实例的弱引用value 是存入的数据强引用图片来源1.set方法源码set 方法先拿到当前线程的引用 Thread.currentThread()通过 getMap(t) 获取该线程内部的 threadLocals即 ThreadLocalMap。如果 Map 已存在以当前 ThreadLocal 实例为 key将 value 存入。如果 Map 不存在线程第一次使用 ThreadLocal则创建一个新的 ThreadLocalMap 并存入数据。// ThreadLocal.set() 简化源码publicvoidset(Tvalue){ThreadtThread.currentThread();// 1. 获取当前线程ThreadLocalMapmapgetMap(t);// 2. 获取当前线程的ThreadLocalMapif(map!null)map.set(this,value);// 3. 以当前ThreadLocal为key存值elsecreateMap(t,value);// 4. 首次创建ThreadLocalMap}ThreadLocalMapgetMap(Threadt){returnt.threadLocals;// 直接返回Thread对象的成员变量}数据实际存储在线程Thread对象的threadLocals中ThreadLocal本身只是一个key2.get方法源码get方法会先获取当前线程。然后获取当前线程的 ThreadLocalMap。以当前 ThreadLocal 实例为 key查找对应的 Entry。找到则返回 value。如果 Map 为 null 或 Entry 为 null调用 setInitialValue() 初始化默认值默认返回 null可重写 initialValue() 方法// ThreadLocal.get() 简化源码publicTget(){ThreadtThread.currentThread();// 1. 获取当前线程ThreadLocalMapmapgetMap(t);// 2. 获取当前线程的ThreadLocalMapif(map!null){ThreadLocalMap.Entryemap.getEntry(this);// 3. 以this为key查找Entryif(e!null){return(T)e.value;// 4. 返回value}}returnsetInitialValue();// 5. Map未初始化则设置初始值}三、ThreadLocalMapThreadLocalMap 是 ThreadLocal 的静态内部类是一个自定义的哈希表专门用来存储线程局部变量。它的 key 是 ThreadLocal 实例value 是线程存储的数据。ThreadLocalMap 的 Entry 继承自 WeakReferenceThreadLocal?staticclassEntryextendsWeakReferenceThreadLocal?{/** 存入的值强引用 */Objectvalue;Entry(ThreadLocal?k,Objectv){super(k);// key是弱引用valuev;// value是强引用}}key 使用弱引用如果 key 是强引用即使我们在代码中把 ThreadLocal 对象设为 nullThreadLocalMap 中的 Entry 仍然强引用了它导致它无法被GC回收从而造成内存泄漏。使用弱引用后当 ThreadLocal 对象除了弱引用外再无其他强引用时GC 会回收它Entry 的 key 变为 null1.ThreadLocalMap的哈希冲突在 ThreadLocal 中采取的是开放地址法的方法来解决哈希冲突。当遇到哈希冲突时会再次进行线性探测探测的意思其实就是去寻找下一个空位。发生哈希冲突时先计算目标位置 index key.threadLocalHashCode (len - 1)如果该位置已被占用├── key 相同 → 直接覆盖└── key 不同 → 向后查找下一个位置index1, index2, …直到找到空位或相同的 key2.过期Key的探测式清理ThreadLocal 的设计者意识到了内存泄漏的风险在 set、get 等方法中内置了探测式清理机制。当发现 key 为 null 的过期 Entry 时会主动将其 value 置为 null帮助 GC 回收。// expungeStaleEntry(i) 的核心逻辑简化privateintexpungeStaleEntry(intstaleSlot){// 1. 将当前位置的 value 置为 nullEntry 置为 nulltab[staleSlot].valuenull;tab[staleSlot]null;// 2. 向后遍历检查并清理其他过期 Entry// 3. 对未过期的 Entry 进行 rehash填补空位// ...}四、ThreadLocalg内存泄漏问题内存泄漏是指程序中不再使用的对象无法被 GC 回收始终占用内存久而久之可能导致 OutOfMemoryError。当 ThreadLocal 对象的外部强引用断开比如生命周期结束或被手动置为 null时GC 会回收它。此时Entry 的 key 变成 null但 value 仍然被 Entry 强引用无法被回收。此时如果线程随之结束Thread 对象被回收ThreadLocalMap 也被回收value 自然释放没问题。如果线程长期存活如线程池 ThreadLocalMap 会一直存在value 堆积越来越多最终导致内存泄漏甚至 OOM。最直接的办法就是使用完ThreadLocal后在finally块中手动调用remove()方法。remove() 方法会主动将 ThreadLocalMap 中该 Entry 的 key 和 value 都清除。// 正确的使用姿势publicclassThreadLocalBestPractice{privatestaticfinalThreadLocalUseruserHoldernewThreadLocal();publicvoidprocess(Useruser){userHolder.set(user);try{// 执行业务逻辑通过 userHolder.get() 获取当前线程的用户对象}finally{userHolder.remove();// ⚠️ 一定要在 finally 中清理}}}五、InheritableThreadLocal父子线程数据传递普通的 ThreadLocal 无法在父子线程间传递数据主线程存的值子线程拿不到。InheritableThreadLocal 解决了这个问题在创建子线程的瞬间它将父线程的本地变量复制给子线程。main 线程是父线程new Thread 创建的是子线程。publicclassInheritableThreadLocalDemo{privatestaticfinalThreadLocalStringthreadLocalnewThreadLocal();privatestaticfinalInheritableThreadLocalStringinheritableTLnewInheritableThreadLocal();publicstaticvoidmain(String[]args){threadLocal.set(父线程-threadLocal的值);inheritableTL.set(父线程-inheritableTL的值);newThread(()-{// ThreadLocal拿不到父线程的数据System.out.println(子线程获取ThreadLocalthreadLocal.get());// InheritableThreadLocal可以拿到System.out.println(子线程获取InheritableinheritableTL.get());}).start();}}实际应用场景1.数据库连接管理每个线程使用自己的 Connection避免事务混乱和连接竞争publicclassConnectionHolder{privatestaticfinalThreadLocalConnectionconnHoldernewThreadLocal();publicstaticConnectiongetConnection(){ConnectionconnconnHolder.get();if(connnull){connDriverManager.getConnection(url,user,password);connHolder.set(conn);}returnconn;}publicstaticvoidcloseConnection(){ConnectionconnconnHolder.get();if(conn!null){conn.close();connHolder.remove();}}}2.用户会话信息存储在Web应用中将当前请求的用户信息存入ThreadLocal整个请求链路无需显式传递用户对象publicclassUserContext{privatestaticfinalThreadLocalUsercurrentUsernewThreadLocal();publicstaticvoidsetUser(Useruser){currentUser.set(user);}publicstaticUsergetUser(){returncurrentUser.get();}publicstaticvoidclear(){currentUser.remove();}}面试问题1.一个线程可以有多个ThreadLocal对象吗可以。线程内部的ThreadLocalMap是一个数组不同的ThreadLocal实例通过哈希计算定位到数组的不同位置所以一个线程可以持有多个ThreadLocal对象2.ThreadLocalMap的key是如何计算出来的每个 ThreadLocal 实例在创建时都会从静态的 AtomicInteger 计数器中获取一个唯一的 threadLocalHashCode。这个值不是简单的自增而是每次加上一个黄金分割数0x61c88647目的是让哈希值分布更加均匀减少冲突。计算数组下标的公式intikey.threadLocalHashCode(table.length-1);这里 table.length 总是 2 的幂所以 (table.length - 1) 相当于取低几位和 HashMap 的索引计算原理一致。3.ThreadLocal的get()方法执行时发生GC后key是否为null如果外部强引用被回收那么发生GC后Entry的key弱引用会被置为null但value仍然存在。此时调用get()方法ThreadLocalMap会通过探测式清理机制跳过key为null的Entry然后返回setInitialValue()的默认值。4.ThreadLocal的set()和get()方法源码中用到了什么设计模式initialValue()方法是一个protected方法默认返回null交给子类去重写这是典型的模板方法模式。spring的RequestContextHolder等框架大量使用了这种模式【这里不太懂用通俗的语言解释】5.InheritableThreadLocal是什么和ThreadLocal有何区别InheritableThreadLocal继承自ThreadLocal可以在创建子线程时自动将父线程的本地变量传递给子线程。区别在于ThreadLocal操作threadLocalsInheritableThreadLocal操作inheritableThreadLocals。传递只在子线程创建时发生一次线程池环境下不适用。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑