简述ThreadLocal

在这里插入图片描述

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();
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值