ThreadLocal

运行时,会在栈中产生两个引用,指向堆中相应的对象。
可以看到,ThreadLocalMap使用ThreadLocal的弱引用作为key,这样一来,当ThreadLocal ref和ThreadLocal之间的强引用断开 时候,即ThreadLocal ref被置为null,下一次GC时,threadLocal对象势必会被回收,这样,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,比如使用线程池,线程使用完成之后会被放回线程池中,不会被销毁,这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永远无法回收,造成内存泄漏。所以当前线程在用完threadlocal后,必须remove。
为什么要使用弱引用?
使用弱引用,是为了更好地对ThreadLocal对象进行回收。如果使用强引用,当ThreadLocal ref = null的时候,意味着ThreadLocal对象已经没用了,ThreadLocal对象应该被回收,但由于Entry中还存着这对ThreadLocal对象的强引用,导致ThreadLocal对象不能回收,可能会发生内存泄漏。
额外保证
对于线程池来说,大部分线程会一直存在在系统的整个生命周期内,那样的话,就会造成value对象出现泄漏的可能。处理的方法是,在ThreadLocalMap进行set(),get(),remove()的时候,都会进行清理:
以getEntry()为例:
private Entry getEntry(ThreadLocal<?> key) { int i = key.threadLocalHashCode & (table.length - 1); Entry e = table[i]; if (e != null && e.get() == key) //如果找到key,直接返回 return e; else //如果找不到,就会尝试清理,如果你总是访问存在的key,那么这个清理永远不会进来 return getEntryAfterMiss(key, i, e); }
getEntryAfterMiss()
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) { Entry[] tab = table; int len = tab.length; while (e != null) { // 整个e是entry ,也就是一个弱引用 ThreadLocal<?> k = e.get(); //如果找到了,就返回 if (k == key) return e; if (k == null) //如果key为null,说明弱引用已经被回收了 //那么就要在这里回收里面的value了 expungeStaleEntry(i); else //如果key不是要找的那个,那说明有hash冲突,这里是处理冲突,找下一个entry i = nextIndex(i, len); e = tab[i]; } return null; }
真正用来回收value的是expungeStaleEntry()方法,在remove()和set()方法中,都会直接或者间接调用到这个方法进行value的清理:
从这里可以看到,ThreadLocal为了避免内存泄露,也算是花了一番大心思。不仅使用了弱引用维护key,还会在每个操作上检查key是否被回收,进而再回收value。
但是从中也可以看到,ThreadLocal并不能100%保证不发生内存泄漏。
比如,很不幸的,你的get()方法总是访问固定几个一直存在的ThreadLocal,那么清理动作就不会执行,如果你没有机会调用set()和remove(),那么这个内存泄漏依然会发生。
因此,一个良好的习惯依然是:当你不需要这个ThreadLocal变量时,主动调用remove(),这样对整个系统是有好处的。
ThreadLocalMap中的Hash冲突处理
它使用的是简单的线性探测法,如果发生了元素冲突,那么就使用下一个槽位存放。
所谓线性探测,就是根据初始key的hashcode值,确定元素在table数组中的位置,如果发现这个位置上,已经有其他key值的元素被占用,则利用固定的算法,寻找一定步长的下个位置,依次判断,直至找到能够存放的位置。
父子线程传递
使用InheritableThreadLocal。
public static void main(String[] args) { ThreadLocal threadLocal = new ThreadLocal(); //改为InheritableThreadLocal threadLocal = new InheritableThreadLocal(); IntStream.range(0,10).forEach(i -> { //每个线程的序列号,希望在子线程中能够拿到 threadLocal.set(i); //这里来了一个子线程,我们希望可以访问上面的threadLocal new Thread(() -> { System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get()); }).start(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } }); }
- 变量的传递是发生在线程创建的时候,如果不是新建线程,而是用了线程池里的线程,就不灵了
- 变量的赋值就是从主线程的map复制到子线程,它们的value是同一个对象,如果这个对象本身不是线程安全的,那么就会有线程安全问题
线程池传递参数
在项目中很少会自己启线程去做逻辑处理,都是初始化线程池使用,那么InheritableThreadLocal就不管用了,需要用到TransmittableThreadLocal。
我们内部对线程池使用TtlExecutors.getTtlExecutor会进行wrap,通过warp后的ExecutorService提交任务,提交任务会对Runnable进行包装。
- 装饰Runnable,将主线程的TTL传入到TtlRunnable的构造方法中
- 将子线程的TTL的值进行备份,将主线程的TTL设置到子线程中(value是对象引用,可能存在线程安全问题);
- 执行子线程逻辑
- 删除子线程新增的TTL,将备份还原重新设置到子线程的TTL中
@Override public void run() { /** * capturedRef是主线程传递下来的ThreadLocal的值。 */ Object captured = capturedRef.get(); if (captured == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null)) { throw new IllegalStateException("TTL value reference is released after run!"); } /** * 1. backup(备份)是子线程已经存在的ThreadLocal变量; * 2. 将captured的ThreadLocal值在子线程中set进去; */ Object backup = replay(captured); try { /** * 待执行的线程方法; */ runnable.run(); } finally { /** * 在子线程任务中,ThreadLocal可能发生变化,该步骤的目的是 * 回滚{@code runnable.run()}进入前的ThreadLocal的线程 */ restore(backup); } } /** * 将快照重做到执行线程 * @param captured 快照 */ public static Object replay(Object captured) { // 获取父线程ThreadLocal快照 final Snapshot capturedSnapshot = (Snapshot) captured; return new Snapshot(replayTtlValues(capturedSnapshot.ttl2Value), replayThreadLocalValues(capturedSnapshot.threadLocal2Value)); } /***************************************************** * 重放TransmittableThreadLocal,并保存执行线程的原值 ****************************************************/ private static WeakHashMap<TransmittableThreadLocalCode<Object>, Object> replayTtlValues(WeakHashMap<TransmittableThreadLocalCode<Object>, Object> captured) { WeakHashMap<TransmittableThreadLocalCode<Object>, Object> backup = new WeakHashMap<TransmittableThreadLocalCode<Object>, Object>(); for (final Iterator<TransmittableThreadLocalCode<Object>> iterator = holder.get().keySet().iterator(); iterator.hasNext(); ) { TransmittableThreadLocalCode<Object> threadLocal = iterator.next(); // backup // 遍历 holder,从 父线程继承过来的,或者之前注册进来的 backup.put(threadLocal, threadLocal.get()); // clear the TTL values that is not in captured // avoid the extra TTL values after replay when run task // 清除本次没有传递过来的 ThreadLocal,和对应值 // -- 第一点:可能会有因为 InheritableThreadLocal 而传递并保留的值 // -- 第二点:保证主线程set过的ThreadLocal不被传递过来。明确其传递是由业务代码控制,就是明确 set 过值的 if (!captured.containsKey(threadLocal)) { iterator.remove(); threadLocal.superRemove(); } } // set TTL values to captured // 将 map 中的值,设置到快照 // 内部调用了 beforeExecute 和 afterExecute 方法。默认不做任何处理 setTtlValuesTo(captured); // call beforeExecute callback // TransmittableThreadLocal 的回调方法,在任务执行前执行 doExecuteCallback(true); return backup; } private static WeakHashMap<ThreadLocal<Object>, Object> replayThreadLocalValues( WeakHashMap<ThreadLocal<Object>, Object> captured) { final WeakHashMap<ThreadLocal<Object>, Object> backup = new WeakHashMap<ThreadLocal<Object>, Object>(); for (Map.Entry<ThreadLocal<Object>, Object> entry : captured.entrySet()) { final ThreadLocal<Object> threadLocal = entry.getKey(); backup.put(threadLocal, threadLocal.get()); final Object value = entry.getValue(); // 如果值是标记已删除,则清除 if (value == threadLocalClearMark) threadLocal.remove(); else threadLocal.set(value); } return backup; } /********************************************* * 恢复备份的原快照 *********************************************/ public static void restore( Object backup) { // 将之前保存的TTL和threadLocal原来的数据覆盖回去 final Snapshot backupSnapshot = (Snapshot) backup; restoreTtlValues(backupSnapshot.ttl2Value); restoreThreadLocalValues(backupSnapshot.threadLocal2Value); } private static void restoreTtlValues( WeakHashMap<TransmittableThreadLocalCode<Object>, Object> backup) { // call afterExecute callback // 调用执行完后回调接口 doExecuteCallback(false); // 移除子线程新增的TTL for (final Iterator<TransmittableThreadLocalCode<Object>> iterator = holder.get().keySet().iterator(); iterator.hasNext(); ) { TransmittableThreadLocalCode<Object> threadLocal = iterator.next(); // 恢复快照时,清除本次传递注册进来,但是原先不存在的 TransmittableThreadLocal // 移除掉所有不在备份里面的TTL数据,应该是为了避免内存泄漏吧 // clear the TTL values that is not in backup // avoid the extra TTL values after restore if (!backup.containsKey(threadLocal)) { iterator.remove(); threadLocal.superRemove(); } } // 重置为原来的数据(就是恢复回备份前的值) // restore TTL values setTtlValuesTo(backup); } private static void setTtlValuesTo( WeakHashMap<TransmittableThreadLocalCode<Object>, Object> ttlValues) { for (Map.Entry<TransmittableThreadLocalCode<Object>, Object> entry : ttlValues.entrySet()) { TransmittableThreadLocalCode<Object> threadLocal = entry.getKey(); // set 的同时,也就将 TransmittableThreadLocal 注册到当前线程的注册表了 threadLocal.set(entry.getValue()); } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构