ThreadLocal的思考

本文详细解析了ThreadLocal的工作原理,包括其线程隔离特性及如何利用弱引用进行垃圾回收。进一步介绍了InheritableThreadLocal如何实现父子线程间的变量共享,以及阿里巴巴开源的TransmittableThreadLocal如何解决线程池中的ThreadLocal值传递问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ThreadLocal的思考

ThreadLocal

ThreadLocal是线程本地变量,每个线程往这个ThreadLocal中读写是线程隔离,互相之间不会影响的。它提供了一种将可变数据通过每个线程有自己的独立副本从而实现线程封闭的机制。

Thread类有一个类型为ThreadLocal.ThreadLocalMap的实例变量threadLocals,也就是说每个线程有一个自己的ThreadLocalMap。ThreadLocalMap参照HashMap的实现,ThreadLocalMap的key为ThreadLocal,value为代码中放入的值(实际上key并不是ThreadLocal本身,而是它的一个弱引用)。每个线程在往某个ThreadLocal里塞值的时候,都会往自己的ThreadLocalMap里存,读也是以某个ThreadLocal作为引用,在自己的map里找对应的key,从而实现了线程隔离。

	/* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocalMap

ThreadLocalMap是ThreadLocal的静态内部类。ThreadLocalMap提供了一种为ThreadLocal定制的高效实现,并且自带一种基于弱引用的垃圾清理机制。
ThreadLocalMap是类似HashMap实现的另一种map实现,有自己的key和value,key为ThreadLocal,value为代码中放入的值。和HashMap一样有一个table数据,有get和set方法,在key的hash冲突时往下寻找对应对象槽位。
ThreadLocalMap里的节点Entry不同于HashMap里面的Entry,继承弱引用,是如下定义的。

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

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

为什么用弱引用

弱引用是Java中四档引用的第三档,比软引用更加弱一些,如果一个对象没有强引用链可达,那么一般活不过下一次GC。当某个ThreadLocal已经没有强引用可达,则随着它被垃圾回收,在ThreadLocalMap里对应的Entry的键值会失效,这为ThreadLocalMap本身的垃圾清理提供了便利。

InheritableThreadLocal

InheritableThreadLocal继承ThreadLocal,ThreadLocal在线程内实现一个局部变量,可以在线程的任何地方来访问,能够减少参数的传递,InheritableThreadLocal在子线程和父线程之间共享线程实例本地变量,也同样是为了减少参数的传递。
Thread类中还有一个类型为ThreadLocal.ThreadLocalMap的变量inheritableThreadLocals:

	/*
     * InheritableThreadLocal values pertaining to this thread. This map is
     * maintained by the InheritableThreadLocal class.
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

ThreadLocal使用的是Thread的实例变量threadLocals;InheritableThreadLocal使用的是Thread的实例变量inheritableThreadLocals。这两个变量类型都为ThreadLocal.ThreadLocalMap,用途不一样。

InheritableThreadLocal的实现如下:

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    /**
     * Computes the child's initial value for this inheritable thread-local
     * variable as a function of the parent's value at the time the child
     * thread is created.  This method is called from within the parent
     * thread before the child is started.
     * <p>
     * This method merely returns its input argument, and should be overridden
     * if a different behavior is desired.
     *
     * @param parentValue the parent thread's value
     * @return the child thread's initial value
     */
    protected T childValue(T parentValue) {
        return parentValue;
    }

    /**
     * Get the map associated with a ThreadLocal.
     *
     * @param t the current thread
     */
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }

    /**
     * Create the map associated with a ThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the table.
     */
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

可以看出,InheritableThreadLocal只是将Thread中的threadLocals替换成inheritableThreadLocals。

InheritableThreadLocal如何实现父子线程间共享

线程在初始化时,会将父线程中的ThreadLocalMap复制到子线程中:

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc) {
        //此处代码无关,忽略
        ...
        if (parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;

        /* Set thread ID */
        tid = nextThreadID();
    }
	static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
    }

将父线程中inheritableThreadLocals变量的ThreadLocalMap中的Entry复制到子线程中:

	private ThreadLocalMap(ThreadLocalMap parentMap) {
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            setThreshold(len);
            table = new Entry[len];

			//将父线程中inheritableThreadLocals变量的ThreadLocalMap中的Entry复制到子线程中
            for (int j = 0; j < len; j++) {
                Entry e = parentTable[j];
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                    if (key != null) {
                        Object value = key.childValue(e.value);
                        Entry c = new Entry(key, value);
                        int h = key.threadLocalHashCode & (len - 1);
                        while (table[h] != null)
                            h = nextIndex(h, len);
                        table[h] = c;
                        size++;
                    }
                }
            }
        }

Transmittable ThreadLocal(TTL)

JDK的InheritableThreadLocal类可以完成父线程到子线程的值传递。但对于使用线程池等会池化复用线程的组件的情况,线程由线程池创建好,并且线程是池化起来反复使用的.这时,ThreadLocal和基于父子线程关系的InheritableThreadLocal值传递已经没有了意义。应用实际上需要的是把任务提交给线程池时的线程的ThreadLocal值传递给实际执行的线程。

TransmittableThreadLocal(TTL) 是Alibaba开源的、用于解决 “在使用线程池等会缓存线程的组件情况下传递ThreadLocal” 问题的 InheritableThreadLocal 扩展.

TransmittableThreadLocal.Transmitter提供了所有TTL值的抓取、回放和恢复方法(即CRR操作):
1.capture方法:抓取线程(线程A)的所有TTL值。
2.replay方法:在另一个线程(线程B)中,回放在capture方法中抓取的TTL值(回放的过程也就是把第一步抓取的线程实例本地变量设置到当前线程的实例本地变量中),并返回 回放前TTL值的备份
3.restore方法:恢复线程B执行replay方法之前的TTL值(即备份,把第二步返回的回放前TTL值重新设置回池化中线程的实例本地变量)

整个过程的完整时序图
在这里插入图片描述代码解析:
capture:

private TtlRunnable(@NonNull Runnable runnable, boolean releaseTtlValueReferenceAfterRun) {
    this.capturedRef = new AtomicReference<Object>(capture());
    this.runnable = runnable;
    this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun;
}
public static Object capture() {
	return new Snapshot(captureTtlValues(), captureThreadLocalValues());
}
private static WeakHashMap<TransmittableThreadLocal<Object>, Object> captureTtlValues() {
	WeakHashMap<TransmittableThreadLocal<Object>, Object> ttl2Value = new WeakHashMap<TransmittableThreadLocal<Object>, Object>();
    for (TransmittableThreadLocal<Object> threadLocal : holder.get().keySet()) {
        ttl2Value.put(threadLocal, threadLocal.copyValue());
    }
    return ttl2Value;
}
private static WeakHashMap<ThreadLocal<Object>, Object> captureThreadLocalValues() {
    final WeakHashMap<ThreadLocal<Object>, Object> threadLocal2Value = new WeakHashMap<ThreadLocal<Object>, Object>();
    for (Map.Entry<ThreadLocal<Object>, TtlCopier<Object>> entry : threadLocalHolder.entrySet()) {
        final ThreadLocal<Object> threadLocal = entry.getKey();
        final TtlCopier<Object> copier = entry.getValue();

        threadLocal2Value.put(threadLocal, copier.copy(threadLocal.get()));
    }
    return threadLocal2Value;
}

repaly:

public void run() {
    Object captured = capturedRef.get();
    if (captured == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null)) {
        throw new IllegalStateException("TTL value reference is released after run!");
    }

    Object backup = replay(captured);
    try {
        runnable.run();
    } finally {
        restore(backup);
    }
}
@NonNull
public static Object replay(@NonNull Object captured) {
    final Snapshot capturedSnapshot = (Snapshot) captured;
    return new Snapshot(replayTtlValues(capturedSnapshot.ttl2Value), replayThreadLocalValues(capturedSnapshot.threadLocal2Value));
}
@NonNull
private static WeakHashMap<TransmittableThreadLocal<Object>, Object> replayTtlValues(@NonNull WeakHashMap<TransmittableThreadLocal<Object>, Object> captured) {
    WeakHashMap<TransmittableThreadLocal<Object>, Object> backup = new WeakHashMap<TransmittableThreadLocal<Object>, Object>();

    for (final Iterator<TransmittableThreadLocal<Object>> iterator = holder.get().keySet().iterator(); iterator.hasNext(); ) {
        TransmittableThreadLocal<Object> threadLocal = iterator.next();

        // backup
        backup.put(threadLocal, threadLocal.get());

        // clear the TTL values that is not in captured
        // avoid the extra TTL values after replay when run task
        if (!captured.containsKey(threadLocal)) {
            iterator.remove();
            threadLocal.superRemove();
        }
    }

    // set TTL values to captured
    setTtlValuesTo(captured);

    // call beforeExecute callback
    doExecuteCallback(true);

    return backup;
}
private static WeakHashMap<ThreadLocal<Object>, Object> replayThreadLocalValues(@NonNull 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;
}

restore:

public static void restore(@NonNull Object backup) {
    final Snapshot backupSnapshot = (Snapshot) backup;
    restoreTtlValues(backupSnapshot.ttl2Value);
    restoreThreadLocalValues(backupSnapshot.threadLocal2Value);
}
private static void restoreTtlValues(@NonNull WeakHashMap<TransmittableThreadLocal<Object>, Object> backup) {
    // call afterExecute callback
    doExecuteCallback(false);

    for (final Iterator<TransmittableThreadLocal<Object>> iterator = holder.get().keySet().iterator(); iterator.hasNext(); ) {
        TransmittableThreadLocal<Object> threadLocal = iterator.next();

        // 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 restoreThreadLocalValues(@NonNull WeakHashMap<ThreadLocal<Object>, Object> backup) {
    for (Map.Entry<ThreadLocal<Object>, Object> entry : backup.entrySet()) {
        final ThreadLocal<Object> threadLocal = entry.getKey();
        threadLocal.set(entry.getValue());
    }
}

注意: 在capture方法中捕获的Snapshot类如下:

		private static class Snapshot {
            final WeakHashMap<TransmittableThreadLocal<Object>, Object> ttl2Value;
            final WeakHashMap<ThreadLocal<Object>, Object> threadLocal2Value;

            private Snapshot(WeakHashMap<TransmittableThreadLocal<Object>, Object> ttl2Value, WeakHashMap<ThreadLocal<Object>, Object> threadLocal2Value) {
                this.ttl2Value = ttl2Value;
                this.threadLocal2Value = threadLocal2Value;
            }
        }

可以看出,除了WeakHashMap<TransmittableThreadLocal, Object>类型的ttl2Value外还有一个WeakHashMap<ThreadLocal, Object>类型的threadLocal2Value。ttl2Value的作用很明显,是当前线程的所有TransmittableThreadLocal变量的集合,但是threadLocal2Value的作用就不那么清晰,通常都是空的,通过查看TTL的javadoc,发现如下一段注释:

ThreadLocal Integration
If you can not rewrite the existed code which use ThreadLocal to TransmittableThreadLocal, register the ThreadLocal instances via the methods registerThreadLocal(ThreadLocal, TtlCopier)/registerThreadLocalWithShadowCopier(ThreadLocal) to enhance the Transmittable ability for the existed ThreadLocal instances.
Below is the example code:


 // the value of this ThreadLocal instance will be transmitted after registered
 Transmitter.registerThreadLocal(aThreadLocal, copyLambda);

 // Then the value of this ThreadLocal instance will not be transmitted after unregistered
 Transmitter.unregisterThreadLocal(aThreadLocal);

从上面一段翻译而来,对于不能讲ThreadLocal转换成TransmittableThreadLocal的情况,提供了ThreadLocal集成机制,可以将ThreadLocal通过registerThreadLocalWithShadowCopier方法注册到Transmitter上的threadLocalHolder变量,从而确保该ThreadLocal会被传递到线程池中真正执行任务的线程。

private static volatile WeakHashMap<ThreadLocal<Object>, TtlCopier<Object>> threadLocalHolder = new WeakHashMap<ThreadLocal<Object>, TtlCopier<Object>>();

TTL用法

1.修饰Runnable/Callable,

public static void main(String[] args) {
    TransmittableThreadLocal<String> transmittableThreadLocal1 = new TransmittableThreadLocal<>();
    TransmittableThreadLocal<String> transmittableThreadLocal2 = new TransmittableThreadLocal<>();
    transmittableThreadLocal1.set("parent1");
    transmittableThreadLocal2.set("parent2");
    System.out.println("in parent, current Thread is: " + Thread.currentThread().getName());
    System.out.println("in parent, transmittablThreadLocal1 is: " + transmittableThreadLocal1.get());
    System.out.println("in parent, transmittablThreadLocal2 is: " + transmittableThreadLocal2.get());

    Runnable runnable = () -> {
        System.out.println("in son,current Thread is: " + Thread.currentThread().getName());
        System.out.println("in son,transmittablThreadLocal1 is: " + transmittableThreadLocal1.get());
        System.out.println("in son,transmittablThreadLocal2 is: " + transmittableThreadLocal2.get());
    };
    TtlRunnable ttlRunnable = TtlRunnable.get(runnable);

    Executors.newCachedThreadPool().submit(ttlRunnable);
}

2.使用TtlExecutors:

public static void main(String[] args) {
    TransmittableThreadLocal<String> transmittableThreadLocal1 = new TransmittableThreadLocal<>();
    TransmittableThreadLocal<String> transmittableThreadLocal2 = new TransmittableThreadLocal<>();
    transmittableThreadLocal1.set("parent1");
    transmittableThreadLocal2.set("parent2");
    System.out.println("in parent, current Thread is: " + Thread.currentThread().getName());
    System.out.println("in parent, transmittablThreadLocal1 is: " + transmittableThreadLocal1.get());
    System.out.println("in parent, transmittablThreadLocal2 is: " + transmittableThreadLocal2.get());

    Runnable runnable = () -> {
        System.out.println("in son,current Thread is: " + Thread.currentThread().getName());
        System.out.println("in son,transmittablThreadLocal1 is: " + transmittableThreadLocal1.get());
        System.out.println("in son,transmittablThreadLocal2 is: " + transmittableThreadLocal2.get());
    };

    ExecutorService executorService = Executors.newFixedThreadPool(1);
    ExecutorService ttlExecutorService = TtlExecutors.getTtlExecutorService(executorService);
    ttlExecutorService.submit(runnable);
}

3.使用Java Agent来修饰JDK线程池实现类

// ## 1. 框架上层逻辑,后续流程框架调用业务 ##
TransmittableThreadLocal<String> context = new TransmittableThreadLocal<String>();
context.set("value-set-in-parent");

// ## 2. 应用逻辑,后续流程业务调用框架下层逻辑 ##
ExecutorService executorService = Executors.newFixedThreadPool(3);

Runnable task = new Task("1");
Callable call = new Call("2");
executorService.submit(task);
executorService.submit(call);

// ## 3. 框架下层逻辑 ##
// Task或是Call中可以读取,值是"value-set-in-parent"
String value = context.get();

Java的启动参数配置示例:
java -javaagent:path/to/transmittable-thread-local-2.x.x.jar
-cp classes
com.alibaba.ttl.threadpool.agent.demo.AgentDemo

<think>好的,我现在要详细思考一下ThreadLocal的内存泄漏问题。首先,我需要理解ThreadLocal的基本原理和它在Java中的作用。ThreadLocal是用来提供线程局部变量的,每个线程访问这个变量时都会得到自己的一个独立副本,这样可以避免多线程环境下的竞争问题。例如,在Web开发中,可以用它来保存每个请求的用户信息,保证线程安全。 接下来,我需要了解为什么ThreadLocal会导致内存泄漏。内存泄漏通常是指程序中不再使用的对象无法被垃圾回收,导致内存被无谓占用。ThreadLocal的内存泄漏可能和它的内部实现有关。根据Java的文档,每个Thread对象内部都有一个ThreadLocalMap,这个Map的键是ThreadLocal实例,值则是线程的局部变量。 这里的关键点是ThreadLocalMap的键是弱引用(WeakReference)的。弱引用在垃圾回收时会被自动回收,即使还有弱引用指向对象。这样设计的目的是当ThreadLocal实例不再被其他地方强引用时,Map中的键会被自动清理,防止内存泄漏。但问题可能出在值上,因为值对象是强引用的。如果线程一直存活(比如线程池中的线程),而ThreadLocal实例已经被回收,那么Map中就会出现键为null的Entry,但值仍然存在强引用,导致无法回收,从而引发内存泄漏。 举个例子,假设有一个线程池中的线程长时间运行,使用了ThreadLocal变量。当ThreadLocal实例被置为null后,由于线程的ThreadLocalMap中的键是弱引用,会被回收,但对应的值仍然存在。如果线程持续存活,这部分内存就无法释放,造成泄漏。 解决这个问题的方法通常是在使用完ThreadLocal后,调用它的remove()方法,手动清除Entry。另外,虽然ThreadLocalMap在set、get等方法时会尝试清理过期Entry,但这并不完全可靠,尤其是在高并发或线程复用的情况下。所以,良好的编程习惯是在try-finally块中使用ThreadLocal,确保finally中调用remove()。 另外,Java的弱引用机制在这里起到了部分作用,但并不能完全避免问题,因为值的强引用依然存在。因此,开发者需要主动管理ThreadLocal的生命周期,避免长时间持有不必要的引用。 总结来说,ThreadLocal内存泄漏的主要原因是ThreadLocalMap中键的弱引用和值的强引用结合,加上线程长时间存活导致无法回收。正确使用remove方法和注意线程生命周期是避免泄漏的关键。</think>ThreadLocal 内存泄漏问题是多线程开发中的经典隐患,其根源在于 ThreadLocal 的弱引用机制与线程生命周期的相互作用。以下是逐步分析: 1. **存储结构剖析** - 每个线程内部维护 `ThreadLocalMap`,键为 `ThreadLocal` 实例(弱引用),值为存储对象(强引用) - 代码示例: ```java ThreadLocal<User> userHolder = new ThreadLocal<>(); userHolder.set(currentUser); // 存入当前线程的Map ``` 2. **泄漏触发条件** - 当 `ThreadLocal` 实例失去强引用时(如置为null) - 线程长时间存活(常见于线程池场景) - 未主动调用 `remove()` 方法清理 3. **内存泄漏过程** ``` ┌───────────┐ ┌───────────────────────┐ │ Thread │ │ ThreadLocal (key) │ │ 本地内存 │──弱→ │ (已被GC回收,变为null) │ │ │ ├───────────────────────┤ │ │ 强→ │ 存储对象 (value) │ └───────────┘ └───────────────────────┘ ``` - 键被回收后,值对象因强引用无法释放 - 单个泄漏微小,但高并发下会导致内存持续增长 4. **解决方案** - **强制清理**:使用后立即调用 `remove()` ```java try { userHolder.set(currentUser); // ...业务逻辑 } finally { userHolder.remove(); // 必须执行清理 } ``` - **防御性编码**:将 `ThreadLocal` 声明为 `static final`,避免重复创建 - **监控手段**:通过内存分析工具检测 `ThreadLocalMap` 的 Entry 数量 5. **最佳实践** - 避免在频繁创建销毁的线程中使用(推荐使用线程池时更要注意清理) - 对线程池线程,在任务执行前后增加清理逻辑 - Java 9+ 建议使用 `withInitial` 初始化: ```java private static final ThreadLocal<User> holder = ThreadLocal.withInitial(() -> new User()); ``` 6. **特殊情况处理** - 使用 `InheritableThreadLocal` 时,子线程会复制父线程变量,需双端清理 - FastThreadLocal(Netty优化实现)通过数组存储规避了哈希碰撞问题 通过主动管理和理解存储结构,可有效规避内存泄漏风险。建议在代码审查时特别关注 ThreadLocal 的使用规范。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值