ThreadLocal存储、泄露、TTL线程传递详解

为什么要有ThreadLocal

我们知道Java线程的出现是为了共享资源,但在线程运行的过程中,他们也希望能够独享某些资源。

ThreadLocal结构和内存泄露分析

在这里插入图片描述

这里其实有点难以理解。这里来说一下要点。

  • 每个Thread有自己的属性threadLocals,是ThreadLocalMap类型,key是threahdLocal实例,value是线程使用这个threadlocal时的value
  • ThreadLocalMap是一个EntryTable,Entry拥有对threadLocal实例的弱引用和value
  • ThreadLocal实例对象拥有两个引用,一个是它本身的强引用ThreadLocalRef@1ThreadLocalRef@2,另外一个则是弱引用CurrentThreadRef@ACurrentThreadRef@B -> map -> entry --> reference。

内存泄露分析:

threadlocal实例引用变化过程

前提条件:

  1. ThreadLocal实例使用完被回收掉。因为ThreadLocalMap的Entry持有的是对ThreadLocal实例的弱引用,所以在方法结束后,ThreadLocal实例就会变成只有弱引用的对象,只要有GC就会被回收掉。
  2. Thread一直存在,如果不存在,那么挂在Thread上的threadlocals肯定都会被回收,就不存在泄露问题了。

原因分析:

当强引用ThreadLocalRef@1ThreadLocalRef@2使用完成会变成不可达,ThreadLocal@1ThreadLocal@2就处于游离态了,会被GC掉,这时候Entry的reference对象也就是null,造成value无法访问,也就是我们说的内存泄露。

那么使用static后,是否可以避免内存泄露呢?

在这里其实需要提一下内存泄露的定义,以下的定义来自wiki:

Memory leak occurs when a computer program incorrectly manages memory allocations in a way that memory which is no longer needed is not released.

可见内存泄露是内存如果是不会再使用没有回收就是内存泄露。

所以本身来说, static 变量是一个GC ROOT, 如果一直没有使用也可以称之为内存泄露。

那为什么我们还建议使用static的ThreadLocal类型呢?

首先,主要的原因还是看怎么去看待它,是实例级别还是全局级别,从线程角度来看,它更符合全局的定义,所以使用了static。

其次是,ThreadLocalMap在get、set、remove时都会进行清理,也是避免了内存泄露,不过作为应用程序最好还是在使用后进行remove清理,否则可能会出现异常情况。

为什么要用弱引用不是强引用?

假如说我们使用强引用的话,会发生怎么样的情况呢? 当ThreadLocalRef引用设置为null后,这个对象因为线程存在无法回收,从而产生内存泄露,当线程或者ThreadLocal使用频繁的情况时,这种无法回收的对象会越来越多,最终甚至可能会导致OOM。

如何在线程之间传递ThreadLocal

现在普遍都会使用阿里的TTL框架(TransmittableThreadLocal),也是我们今天的主角。
其实JDK默认提供了父子线程的传递。

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

首先我们看下InheritableThreadLocal是怎么复制的,关键的代码在Thread.init()方法中.

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
     ...
     // 如果是是可继承的threadlocal并且父线程的inheritableThreadLocal不为空,就复制到一个新的map中
     if (inheritThreadLocals && parent.inheritableThreadLocals != null)
         this.inheritableThreadLocals =
             ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
     ...
 }

create方法调用了new ThreadLocalMap(ThreadLocalMap parentMap),所以我们来看下具体的方法体。

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

    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++;
            }
        }
    }
}

总结一下,InheritableThreadLocal是新建了一个Map和Entry,但是里面的key和value并没有clone。

阿里的TTL理解起来并不容易,所以我们先讲讲原理。

在这里插入图片描述
重点说明:

  1. 首先是注册两个Transmittee的实现。Transmittee的有两部分组成,C - 获取的数据 和 B - 原来获取数据的Backup,这两个类型对于ttlTransmittee都是HashMap。
  2. 当ThreadLocal开始进行set时,除了原始的threadlocal的set功能,还会调用addThisToHolder。holder是一个静态的可继承的ThreadLocal变量,所以每个线程都可以有自己的。这个变量里存储的类型是WeakHashMap,key是TransmitThreadLocal,value是null。每个线程都可以有自己的WeakHashMap,可以有多个TransmitThreadLocal变量。
  3. 在ttlTransmittee的操作中,会对holder进行循环遍历。capture是获取holder中的threadlocal和value,replay是先备份,然后设置value,restore则是对backup进行恢复。
  4. 最后就是TtlRunnable中调用Transmitter的caputre/replay/restore。capture是在实例化的时候调用的,值得注意的是实例化其实在父线程中完成的,然后运行过程中获取父线程实例的值,之后在replay和restore。

如果只想了解原理,那么到这里就可以结束了,对代码细节感兴趣的同学我们继续向下。

TransmittableThreadLocal继承了InheritableThreadLocal,所以具备父子复制的能力。然后我们接着TTL官方的图来解释。
在这里插入图片描述
如果以装饰模式来看待这个问题,就会很容易发现TtlRunnable是真正把TransmittableThreadLocal起来的类。TtlRunnable的关键代码是:

public final class TtlRunnable implements Runnable, TtlWrapper<Runnable>, TtlEnhanced, TtlAttachments {
    // 从父线程传递的值
    private final AtomicReference<Object> capturedRef;
    // 原始的runnable
    private final Runnable runnable;
    // 是否运行后清除
    private final boolean releaseTtlValueReferenceAfterRun;

    private TtlRunnable(@NonNull Runnable runnable, boolean releaseTtlValueReferenceAfterRun) {
        // 调用了Transmitter静态的capture 先下结论是会返回父线程的Transmitter和Value(被称为Snapshot),之后我们看是如何返回的
        this.capturedRef = new AtomicReference<>(capture());
        this.runnable = runnable;
        this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun;
    }

	public void run() {
		// 获取Snapshot
        final Object captured = capturedRef.get();
        // 如果snapshot为null或者要释放没成功,就抛出异常
        if (captured == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null)) {
            throw new IllegalStateException("TTL value reference is released after run!");
        }

		// 执行前backup一下
        final Object backup = replay(captured);
        try {
        	// 任务执行
            runnable.run();
        } finally {
        	// 恢复backup
            restore(backup);
        }
    }
    ... 

我们看到在run的方法里执行了获取的capture -> replay -> restore,从Transmitter的注释来看,他们分别代表了以下含义:

Thread A capture: 捕获当前线程的threadlocal的值
Thread B replay: 将capture到的线程重放到当前线程(完成父子线程的值传递),返回当前线程的backup
Thread B restore: 将backup的线程值恢复到当前线程

阿里官方文档称这个操作是CRR,从我搜索的资料看应该他们自己的简写,没有相关的专业术语。

接下来我们来解析这个过程是怎么发生的。一切的一切要从com.alibaba.ttl.TransmittableThreadLocal#set说起。

@Override
public final void set(T value) {
    if (!disableIgnoreNullValueSemantics && null == value) {
        // may set null to remove value
        remove();
    } else {
        // 调用ThreadLocal的set方法 完成当前线程的threadlocalMap创建工程
        super.set(value);
        // 这一步是关键 把当前TransmittableThreadLocal添加到了holder
        addThisToHolder();
    }
}

com.alibaba.ttl.TransmittableThreadLocal#addThisToHolder方法:

@Override
private void addThisToHolder() {
    if (!holder.get().containsKey(this)) {
        holder.get().put((TransmittableThreadLocal<Object>) this, null); // WeakHashMap supports null value.
    }
}

我们看看holder的结构是什么:

private static final InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>, ?>> holder =
new InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>, ?>>() {
    @Override
    protected WeakHashMap<TransmittableThreadLocal<Object>, ?> initialValue() {
        return new WeakHashMap<>();
    }

    @Override
    protected WeakHashMap<TransmittableThreadLocal<Object>, ?> childValue(WeakHashMap<TransmittableThreadLocal<Object>, ?> parentValue) {
        return new WeakHashMap<TransmittableThreadLocal<Object>, Object>(parentValue);
    }
};

holder是一个InheritableThreadLocal,能够支持父子类型传递。里面存储的是WeakHashMap,可以保存多个TransmittableThreadLocal。

放入holder之后就完成了ThreadLocal的关联。接下来让我们看看Transmitter的代码:

public static class Transmitter {

 
 @NonNull
 public static Object capture() {
     // 新建一个HashMap,来存放Transmittee和它的值
     final HashMap<Transmittee<Object, Object>, Object> transmittee2Value = new HashMap<>(transmitteeSet.size());
     // 循环transmitteeSet
     for (Transmittee<Object, Object> transmittee : transmitteeSet) {
         try {
         	 // 放入到新建的map中
             transmittee2Value.put(transmittee, transmittee.capture());
         } catch (Throwable t) {
             if (logger.isLoggable(Level.WARNING)) {
                 logger.log(Level.WARNING, "exception when Transmitter.capture for transmittee " + transmittee +
                         "(class " + transmittee.getClass().getName() + "), just ignored; cause: " + t, t);
             }
         }
     }
     // 返回快照
     return new Snapshot(transmittee2Value);
 }


 @NonNull
 public static Object replay(@NonNull Object captured) {
     // 快照是从capture拿到的HashMap,里面存储的是transmittee和原来捕获的值
     final Snapshot capturedSnapshot = (Snapshot) captured;
	 // 新建一个value
     final HashMap<Transmittee<Object, Object>, Object> transmittee2Value = new HashMap<>(capturedSnapshot.transmittee2Value.size());
     // 循环快照
     for (Map.Entry<Transmittee<Object, Object>, Object> entry : capturedSnapshot.transmittee2Value.entrySet()) {
         Transmittee<Object, Object> transmittee = entry.getKey();
         try {
             // 获取原来transmittee捕获的值
             Object transmitteeCaptured = entry.getValue();
             // 放入transmittee和值,这里调用replay的作用按照原来的有两个作用,一个是用来备份,一个是用来将捕获的值放到当前线程中
             transmittee2Value.put(transmittee, transmittee.replay(transmitteeCaptured));
         } catch (Throwable t) {
             if (logger.isLoggable(Level.WARNING)) {
                 logger.log(Level.WARNING, "exception when Transmitter.replay for transmittee " + transmittee +
                         "(class " + transmittee.getClass().getName() + "), just ignored; cause: " + t, t);
             }
         }
     }
     // 返回备份的值
     return new Snapshot(transmittee2Value);
 }
 ...

 public static void restore(@NonNull Object backup) {
     // 循环backup的值
     for (Map.Entry<Transmittee<Object, Object>, Object> entry : ((Snapshot) backup).transmittee2Value.entrySet()) {
         Transmittee<Object, Object> transmittee = entry.getKey();
         try {
             // 获取backup的值
             Object transmitteeBackup = entry.getValue();
             // 恢复backup的值到当前线程
             transmittee.restore(transmitteeBackup);
         } catch (Throwable t) {
             if (logger.isLoggable(Level.WARNING)) {
                 logger.log(Level.WARNING, "exception when Transmitter.restore for transmittee " + transmittee +
                         "(class " + transmittee.getClass().getName() + "), just ignored; cause: " + t, t);
             }
         }
     }
 }

这里出现了一个新的角色: Transmittee。Transmittee是一个接口,实现是匿名类的方式,有两个实现。

TransmittableThreadLocal的实现:

private static final Transmittee<HashMap<TransmittableThreadLocal<Object>, Object>, HashMap<TransmittableThreadLocal<Object>, Object>> ttlTransmittee = new Transmittee<HashMap<TransmittableThreadLocal<Object>, Object>, HashMap<TransmittableThreadLocal<Object>, Object>>() {
    @NonNull
    public HashMap<TransmittableThreadLocal<Object>, Object> capture() {
        // 从holder中取出来TransmitThreadLocal的数量 新建一个HashMap来存储
        HashMap<TransmittableThreadLocal<Object>, Object> ttl2Value = new HashMap(((WeakHashMap)TransmittableThreadLocal.holder.get()).size());
        // 循环holder中的TransmitThreadLocal
        Iterator var2 = ((WeakHashMap)TransmittableThreadLocal.holder.get()).keySet().iterator();
        while(var2.hasNext()) {
            TransmittableThreadLocal<Object> threadLocal = (TransmittableThreadLocal)var2.next();
            // 放入threadlocal和threadlocal复制的值
            ttl2Value.put(threadLocal, threadLocal.copyValue());
        }

        return ttl2Value;
    }

    @NonNull
    public HashMap<TransmittableThreadLocal<Object>, Object> replay(@NonNull HashMap<TransmittableThreadLocal<Object>, Object> captured) {
        // 获取holder的size 新建backup的HashMap
        HashMap<TransmittableThreadLocal<Object>, Object> backup = new HashMap(((WeakHashMap)TransmittableThreadLocal.holder.get()).size());
        // 循环holder中的变量
        Iterator<TransmittableThreadLocal<Object>> iterator = ((WeakHashMap)TransmittableThreadLocal.holder.get()).keySet().iterator();

        while(iterator.hasNext()) {
            TransmittableThreadLocal<Object> threadLocal = (TransmittableThreadLocal)iterator.next();
            // 放入backup的threadLocal和threadLocal的值
            backup.put(threadLocal, threadLocal.get());
            // 如果父线程中没有这个threadlocal 就从holder的循环中移除掉 
            if (!captured.containsKey(threadLocal)) {
                iterator.remove();
                threadLocal.superRemove();
            }
        }
		
		// 将captured的value设置到capture的threadlocal里
        TransmittableThreadLocal.Transmitter.setTtlValuesTo(captured);
        TransmittableThreadLocal.doExecuteCallback(true);
        return backup;
    }

    @NonNull
    public HashMap<TransmittableThreadLocal<Object>, Object> clear() {
        return this.replay(new HashMap(0));
    }

    public void restore(@NonNull HashMap<TransmittableThreadLocal<Object>, Object> backup) {
        TransmittableThreadLocal.doExecuteCallback(false);
        // 循环holder
        Iterator<TransmittableThreadLocal<Object>> iterator = ((WeakHashMap)TransmittableThreadLocal.holder.get()).keySet().iterator();

        while(iterator.hasNext()) {
            TransmittableThreadLocal<Object> threadLocal = (TransmittableThreadLocal)iterator.next();
            // 如果backup的没有包括此threadlocal 就移除掉
            if (!backup.containsKey(threadLocal)) {
                iterator.remove();
                threadLocal.superRemove();
            }
        }

		// 将backup的值进行还原
        TransmittableThreadLocal.Transmitter.setTtlValuesTo(backup);
    }
};

threadlocal的实现大体一致,只不过是存储的类型从TransmittableThreadLocal变成了ThreadLocal,这里就不进行展开了。
总的来说,TTL实现的功能并不复杂,但代码很复杂,难以理解。这或许是我们常说的那句: 美都是肤浅的。你怎么看? 欢迎交流讨论。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java中,ThreadLocal是一种线程局部变量,它可以在多个线程存储和访问数据,每个线程都有自己独立的副本。然而,ThreadLocal的数据在子线程中默认是无法传递的。但是,可以通过一些特殊的ThreadLocal实现类来实现ThreadLocal数据在子线程中的传递。 一种实现方式是使用InheritableThreadLocal类。InheritableThreadLocalThreadLocal的一个子类,它允许子线程继承父线程ThreadLocal变量。当一个线程创建子线程时,子线程会自动拥有父线程的InheritableThreadLocal变量的副本。这样,父线程中设置的ThreadLocal变量的值可以在子线程中访问到。 另一种实现方式是使用TransmittableThreadLocal类。TransmittableThreadLocal是一个第三方库,它提供了更强大的功能,可以在线程传递ThreadLocal变量的值。它通过在线程切换时保存和恢复ThreadLocal变量的值来实现传递。使用TransmittableThreadLocal,可以在父线程中设置ThreadLocal变量的值,并在子线程中访问到这个值。 下面是使用InheritableThreadLocal和TransmittableThreadLocal传递ThreadLocal变量到子线程的示例代码: ```java // 使用InheritableThreadLocal传递ThreadLocal变量到子线程 ThreadLocal<String> threadLocal = new InheritableThreadLocal<>(); threadLocal.set("Hello, World!"); Thread thread = new Thread(() -> { String value = threadLocal.get(); System.out.println("Value in child thread: " + value); }); thread.start(); // 使用TransmittableThreadLocal传递ThreadLocal变量到子线程 ThreadLocal<String> threadLocal = new TransmittableThreadLocal<>(); threadLocal.set("Hello, World!"); Thread thread = new Thread(() -> { String value = threadLocal.get(); System.out.println("Value in child thread: " + value); }); thread.start(); ``` 在上面的代码中,我们首先创建了一个ThreadLocal变量,并在父线程中设置了它的值。然后,我们创建了一个子线程,并在子线程中获取并打印了ThreadLocal变量的值。使用InheritableThreadLocal或TransmittableThreadLocal,我们可以在子线程中成功访问到父线程设置的ThreadLocal变量的值。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值