ThreadLoca与FastThreadLocal

ThreadLocal造成内存泄露的原因

内存溢出,指的是程序在运行过程中试图分配更多内存,但系统已经没有足够的内存可供分配。

内存泄露,是指程序在申请内存后无法释放不再使用的内存空间。

内存泄露是程序问题,合理分配和使用资源。内存溢出是内存管理问题。

ThreadLocal的实现原理是每一个Thread维护一个ThreadLocalMap,key为使用弱引用的ThreadLocal实例,value为线程变量的副本。

ThreadLocal的结构

根据源码看下ThreadLocal的整个结构

ThreadLocal<String> threadLocal = new ThreadLocal<>();
String str = threadLocal.get();

/**
	* 返回当前线程对该线程局部变量副本中的值。
	* 如果该变量在当前线程中尚未赋值,则首先初始化为通过调用{@link #initialValue}方法所返回的值。
	*
	* @return the current thread's value of this thread-local
*/
public T get() {
	// 获取当前线程的引用 t=CurrentThreadRe
	Thread t = Thread.currentThread();
	// 从当前线程中取出 ThreadLocalMap 
	ThreadLocalMap map = getMap(t);
	if (map != null) {
	ThreadLocalMap.Entry e = map.getEntry(this);
		if (e != null) {
			@SuppressWarnings("unchecked")
			T result = (T)e.value;
			 return result;
			}
		}
   // 初始化
	return setInitialValue();
}

首先分析下ThreadLocalMap,既然定义为xxMap,那数据格式一般键值对存储结构,

根据 map.getEntry(this); 的用法,可以分析出键值对的key 为this,this指的当前ThreadLocal对象。而值是ThreadLocalMap.Entry,Entry中有一个value属性,这个value也是最终return的内容。

不看代码,推理一下逆向set过程
1.获取当前线程
2.从当前线程中取出ThreadLocalMap(==null 则初始化ThreadLocalMap)
3.执行map.getEntry(this); 取出Map中的Entry
4.如果Entry == null,则初始化
5.修改Entry 的value值

验证下源码

public void set(T value) {
	Thread t = Thread.currentThread();
	ThreadLocalMap map = getMap(t);
	if (map != null) {
		map.set(this, value);
	} else {
		createMap(t, value);
	}
}

既然是Map对象,便会有Hash冲突的风险,ThreadLocalMap使用线性探测来解决。

扩容问题
ThreadLocalMap初始化容量为16,当元数量超过阈值后,会进行x2扩容。

int newLen = oldLen * 2;

一般情况下 这个行为并不会很频繁。ThreadLocalMap对象会一直伴随着Thread的整个生命周期,扩容后会一直保持这个大小。

内容泄露问题

内存泄露,是指程序在申请内存后无法释放不再使用的内存空间。

通常情况下,当线程执行完毕或ThreadLocal实例不再被任何强引用引用时,其关联的线程局部变量应该可以被回收。然而由于ThreadLocalMap的内部实现中键是弱引用,当外部对该ThreadLocal实例没有强引用,键是可以被垃圾回收的,但对应的值却是强引用,这个值不会被垃圾回收器回收。在这里插入图片描述、
如上图所示,出现了一些变量 ThreadLocalRef、CurrentThreadRef、CurrentThread、ThreadLocalMap、ThreadLocal、Entry、key、value。其中ThreadLocalRef、CurrentThreadRef 是对CurrentThread和ThreadLocal的引用,栈帧返回后会自动被回收。CurrentThread是线程,ThreadLocalMap是Thread的一个属性。

ThreadLocal 存在两个引用,一个强引用,在栈上;一个弱引用,在ThreadLocalMap中。当栈空间被回收后,ThreadLocal只剩下了一个弱引用,如果发生了GC,那ThreadLocal就会被回收。而对应的value则还是强引用,被绑定在ThreadLocalMap.Entry上。jdk1.8之前如果当前线程迟迟不结束的话,这些key为null的Entry的value就会一直存在。jdk1.8之后,虽然操作ThreadLocal是会主动触发清理,但是如果不触发的话,还是会造成内存泄露的风险。

Thread Ref -> Thread -> ThreadLocalMap -> Entry -> value

这里有一个误区,请看下面对ThreadLocal的定义

 private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();

static 关键字,会让人觉得这是一个静态常量,而静态常量属于类元信息,一个线程的值修改后其它线程也会被改。但是如果理解threadLocal后,每个线程都有自己独立的ThreadLocalMap,而threadLocal只是作为一个key,并不能影响value。

FashtThreadLocal

Netty为提升性能而自定义的ThreadLocal实现

FastThreadLocal的数据结构

FastThreadLocal改用InternalThreadLocalMap,其中是用Object[] indexedVariables;作为数据存储。

Q:数组总是如何定位数据的呢?

A:每一个FastThreadLocal都有一个下标,这个Index是全局唯一的,通过物理层保障这个唯一。

FastThreadLocal是如何优化的

使用数组代替hash变,避免hash冲突而造成的额外性能损失,同时index定位也提升了搜索效率。

至于如何解决内存泄露问题,看到FashtThreadLocal的用法就明了了。

FastThreadLocal用法

三件套

FastThreadLocalThread
FastThreadLocalRunnable
FastThreadLocal

选择对应的Factory,便可以创建对应的FastThreadLocalThread,此时FastThreadLocal才能被正确使用。

    public static InternalThreadLocalMap get() {
        Thread thread = Thread.currentThread();
        if (thread instanceof FastThreadLocalThread) {
            return fastGet((FastThreadLocalThread) thread);
        } else {
            return slowGet();
        }
    }

最重要的一步,执行完任务后,

class FastThreadLocalRunnable implements Runnable {
    private final Runnable runnable;

    private FastThreadLocalRunnable(Runnable runnable) {
        this.runnable = ObjectUtil.checkNotNull(runnable, "runnable");
    }

    @Override
    public void run() {
        try {
            runnable.run();
        } finally {
            FastThreadLocal.removeAll();
        }
    }
....
}

就此 主动的完成了删除无用value的过程。

ThreadLocal的销毁是与Thread绑定在一起的,如果一直不进行。而FastThreadLocal的销毁是与Task绑定在一起了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值