震惊:Netty竟然对ThreadLocal做出这种事...

本文详细探讨了ThreadLocal内存泄露问题,分析了弱引用的原因及其对内存泄漏的影响,并提出了最佳实践。此外,文章还介绍了Netty中的FastThreadLocal,说明了其在无需手动remove、避免伪共享问题以及提高访问性能等方面的优化策略。通过源码分析,阐述了FastThreadLocal如何实现更高效的线程局部变量管理。
摘要由CSDN通过智能技术生成

目录

1、ThreadLocal

ThreadLocal内存泄露?

既然是ThreadLocal的弱引用导致了内存泄漏,那为什么不使用强引用?

最佳实践

源码

2、FastThreadLocal

优化1:不需要手动remove

优化2:利用字节填充避免伪共享问题

优化3:使用常量下标在数组中定位元素替代ThreadLocal通过哈希和哈希表

源码


1、ThreadLocal

ThreadLocal类提供了线程局部 (thread-local) 变量。这些变量与普通变量不同,每个线程都可以通过其 get 或 set方法来访问自己的独立初始化的变量副本。

Thread类中包含ThreadLocalMap对象,ThreadLocalMap是ThreadLocal的静态内部类,ThreadLocalMap中的对象都是以ThreadLocal对象作为key存储对应的value。

ThreadLocal内存泄露?

ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用,系统GC时这个ThreadLocal就会被回收,ThreadLocalMap中就出现了key为null的Entry,如果线程不结束,这些Entry的value就会存在一条强引用链:Thread -> ThreadLocal.ThreadLocalMap -> Entry[] -> Enrty -> value,造成内存泄漏。 

ThreadLocalMap针对此做了一些优化,比如在调用ThreadLocal的get()、set()、remove()时都会清理ThreadLocalMap中所有key为null的value。但不能避免内存泄漏的发生,比如分配使用了ThreadLocalMap后不再调用get()、set()、remove()方法。

java对象的引用包括 : 强引用、软引用、弱引用、虚引用 

  • 强引用:我们平时一般都是这种引用,当一个对象被一个或一个以上的引用变量所引用时,它处于可达状态,不可能被系统垃圾回收机制回收。
  • 软引用:软引用需要通过SoftReference类来实现,当系统内存空间足够时,它不会被系统回收,当系统内存空间不够时,系统可能回收它。 
  • 弱引用:弱引用通过WeakReference类实现,当系统垃圾回收机制运行时,不管系统内存是否足够,总会回收该对象所占用的内存。
  • 虚引用:虚引用通过PhantomReference类实现,虚应用完全类似于没有引用。虚引用主要用来跟踪对象被垃圾回收器回收的活动。

既然是ThreadLocal的弱引用导致了内存泄漏,那为什么不使用强引用?

ThreadLocalMap本身并没有为外界提供取出和存放数据的API,我们所能获得数据的方式只有通过ThreadLocal类提供的API来间接的从ThreadLocalMap取出数据,所以,当我们用不了key(ThreadLocal对象)的API也就无法从ThreadLocalMap里取出指定的数据。使用强引用的话,如果引用ThreadLocal的对象已经回收了,我们就无法在get到ThreadLocalMap中的数据,也就是说已经有部分数据无效了,但ThreadLocalMap还持有对ThreadLocal的强引用,引用链Thread -> ThreadLocal.ThreadLocalMap -> Entry[] -> Enrty -> key,当这个线程还未结束时,他持有的强引用,包括递归下去的所有强引用都不会被垃圾回收器回收,导致Entry内存泄漏。

比如下面这个例子,当Test对象被回收时,没法通过get方法使用ThreadLocalMap中的数据了,那保存数据的Entry对象就没用了,所以要想办法让系统自动回收对应的Entry对象。

但是让Entry对象或其中的value对象做为弱引用都是非常不合理的。所以,让key(threadLocal对象)为弱引用,自动被垃圾回收,key就变为null了,下次,我们就可以通过Entry不为null,而key为null来判断该Entry对象该被清理掉了。

public class ThreadLocalTest {

    public static void main(String[] args) throws InterruptedException {
        doSomeWork(); //如果给它一个引用,比如Test test = doSomeWork(),System.gc()就不会回收threadLocal,直到方法结束才可能会被回收
        System.gc();
        TimeUnit.SECONDS.sleep(1);
        Thread thread = Thread.currentThread();
        System.out.println(thread);
    }

    private static Test doSomeWork() {
        Test test = new Test();
        System.out.println("int value:" + test.get());
        return test;
    }
}

class Test {
    private ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 1);

    public Integer get() {
        return threadLocal.get();
    }
}

     

最佳实践

  • 每次使用完ThreadLocal,调用remove()进行清理。
try {
// 业务逻辑,threadLocal#get, threadLocal#set
} finally {
    threadLocal.remove(); 
}

阿里规范:

15.【参考】 ThreadLocal 无法解决共享对象的更新问题, ThreadLocal 对象建议使用 static
修饰。这个变量是针对一个线程内所有操作共享的,所以设置为静态变量,所有此类实例共享
此静态变量 ,也就是说在类第一次被使用时装载,只分配一块存储空间,所有此类的对象 ( 只
要是这个线程内定义的 ) 都可以操控这个变量。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值