为什么要有ThreadLocal
我们知道Java线程的出现是为了共享资源,但在线程运行的过程中,他们也希望能够独享某些资源。
ThreadLocal结构和内存泄露分析
这里其实有点难以理解。这里来说一下要点。
- 每个Thread有自己的属性
threadLocals
,是ThreadLocalMap
类型,key是threahdLocal实例,value是线程使用这个threadlocal时的value - ThreadLocalMap是一个EntryTable,Entry拥有对threadLocal实例的弱引用和value
- ThreadLocal实例对象拥有两个引用,一个是它本身的强引用
ThreadLocalRef@1
和ThreadLocalRef@2
,另外一个则是弱引用CurrentThreadRef@A
和CurrentThreadRef@B
-> map -> entry --> reference。
内存泄露分析:
前提条件:
- ThreadLocal实例使用完被回收掉。因为ThreadLocalMap的Entry持有的是对ThreadLocal实例的弱引用,所以在方法结束后,ThreadLocal实例就会变成只有弱引用的对象,只要有GC就会被回收掉。
- Thread一直存在,如果不存在,那么挂在Thread上的threadlocals肯定都会被回收,就不存在泄露问题了。
原因分析:
当强引用ThreadLocalRef@1
和ThreadLocalRef@2
使用完成会变成不可达,ThreadLocal@1
和ThreadLocal@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理解起来并不容易,所以我们先讲讲原理。
重点说明:
- 首先是注册两个Transmittee的实现。Transmittee的有两部分组成,C - 获取的数据 和 B - 原来获取数据的Backup,这两个类型对于ttlTransmittee都是HashMap。
- 当ThreadLocal开始进行set时,除了原始的threadlocal的set功能,还会调用
addThisToHolder
。holder是一个静态的可继承的ThreadLocal变量,所以每个线程都可以有自己的。这个变量里存储的类型是WeakHashMap,key是TransmitThreadLocal,value是null。每个线程都可以有自己的WeakHashMap,可以有多个TransmitThreadLocal变量。 - 在ttlTransmittee的操作中,会对holder进行循环遍历。capture是获取holder中的threadlocal和value,replay是先备份,然后设置value,restore则是对backup进行恢复。
- 最后就是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实现的功能并不复杂,但代码很复杂,难以理解。这或许是我们常说的那句: 美都是肤浅的。你怎么看? 欢迎交流讨论。