class Thread {
//内部持有ThreadLocalMap
ThreadLocalMap threadLocals;
}
class ThreadLocal<T>{
public T get() {
//⾸先获取线程持有的ThreadLocalMap
ThreadLocalMap map = Thread.currentThread().threadLocals;
//在ThreadLocalMap中查找变量
Entry e = map.getEntry(this);
return e.value;
}
}
static class ThreadLocalMap{
//内部是数组⽽不是Map
Entry[] table;
//根据ThreadLocal查找Entry
Entry getEntry(ThreadLocal key){
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
}
//Entry定义
static class Entry extends WeakReference<ThreadLocal> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
Java的实现里面ThreadLocalMap是属于Thread。在Java的实现方案里面,ThreadLocal仅仅是一个代理工具类,内部并不持有任何与线程相关的数据,所有和线程相关的数据都存储在Thread里面,这样的设计容易理解。而从数据的亲缘性上来讲,ThreadLocalMap属于Thread也更加合理。当然还有一个更加深层次的原因,那就是不容易产生内存泄露。在我们的设计方案中,ThreadLocal持有的Map会持有Thread对象的引用,这就意味着,只要ThreadLocal对象存在,那么Map中的Thread对象就永远不会被回收。ThreadLocal的生命周期往往都比线程要长,所以这种设计方案很容易导致内存泄露。 而Java的实现中Thread持有ThreadLocalMap,而且ThreadLocalMap里对ThreadLocal的引用还是弱引用(WeakReference,和软引用比,它的生命周期更短,在GC的过程中,一旦发现有弱引用的对象,不管当前内存空间是否足够,都会回收它的内存,主要目的是可以实现提前释放ThreadLocal),所以只要Thread对象可以被回收,那么ThreadLocalMap就能被回收。
Java的这种实现方案虽然看上去复杂一些,但是更加安全。Java的ThreadLocal实现应该称得上深思熟虑了,不过即便如此深思熟虑,还是不能百分百地让程序员避免内存泄露,例如在线程池中使用ThreadLocal,如果不谨慎就可能导致内存泄露。
在线程池中使用ThreadLocal为什么可能导致内存泄露呢?原因就出在线程池中线程的存活时间太长,往往都是和程序同生共死的,这就意味着Thread持有的ThreadLocalMap一直都不会被回收,再加上ThreadLocalMap中的Entry对ThreadLocal是弱引用(WeakReference),所以只要ThreadLocal结束了自己的生命周期是可以被回收掉的。但是Entry中的Value却是被Entry强引用的,所以即便Value的生命周期结束了,Value也是无法被回收的,从而导致内存泄露。
那在线程池中,我们该如何正确使用ThreadLocal呢?其实很简单,既然JVM不能做到自动释放对Value的强引用,那我们手动释放就可以了。如何能做到手动释放呢?估计你马上想到try{}finally{}方案了,这个简直就是手动释放资源的利器。示例的代码如下。
ExecutorService es;
ThreadLocal tl;
es.execute(()->{
//ThreadLocal增加变量
tl.set(obj);
try {
// 省略业务逻辑代码
}finally {
//⼿动清理ThreadLocal
tl.remove();
}
});
ThreadLocal<String> ctx1 = new ThreadLocal<>();
ThreadLocal<String> ctx2 = new ThreadLocal<>();
ctx1.set("123");
ctx2.set("789");
ctx1.get();
ctx2.get();