ThreadLocal有内存泄漏?Java的作者有这么low?不清楚的还是别乱说话了

103 阅读3分钟

这是我参与2022首次更文挑战的第1天,活动详情查看:2022首次更文挑战

0.背景

网上经常有人说ThreadLocal有内存泄漏的问题。 我先说结论:呵呵,你觉得写Java的一群大佬,能让一个内存泄漏的bug存在吗?java的源代码的每一行代码,都是凝聚了最顶尖的设计和智慧。

我们先来复习下threadlocal的设计。

Thread的一个属性ThreadLocalMap Map是的key就是弱引用,是ThreadLocal,value就是任何值了。 我们通过ThreadLocal tl = new ThreadLocalI();进行传递值。

听着有点晕?怎么使用这种设计啊。是不是有些过度设计了。ok,那假设你要给Java开发这么一个组件,会不会比它更好呢?

1.自己开发一个线程内数据存储组件

需求:

只对当前线程进行一个数据的读写操作。

技术设计:

因为是线程独有的数据,我们假设直接Thread类下吧。我们给Thread增加一个属性,叫做myValueMap吧 map假设是HashMap类型。 我们肯定支持多个变量,那我们用String做key吧。value就是Object。

那我们使用时,就用System.currentThread().set("name","yasin"); 使用时时System.currentThread().get("name");

是不是感觉也能用。是的,没问题,不过用string做为key,视乎不太优雅,代码中充斥这这种太自由的气息,没有办法做权限控制。你可以读写任何string的数据。

那我们能不能用对象做key呢,把对象局限在自己的边界里呢? ok,那我们搞个类叫MyThreadValueKey吧。 那这个类,是不是太简单了,啥事都没干。那每次System.cu....感觉太麻烦了呢?我们不如把这些逻辑封装到MyThreadValueKey里吧。 这样每次使用

MyThreadValueKey key = new MyThreadValueKey();
key.set("yasin");
String name = key.get();

哇,要是设计成这样,简直很好用啊 我们只要实现下set\get方法

public void set(){
    Thread thread = System.currentThread();
    Map map = thread.getMyValueMap();//还记得我们假设在Thread中的map吗?
    if (map != null)
       map.set(this, value);
    else
       createMap(t, value);
}

这样是不是就解决了我们的问题。完美。

2.传说中的内存泄漏

当我得意洋洋的把这个设计思路跟我的一个高级专家朋友讨论时,提了一个问题,我们的Server端的处理网络请求的Thread一般都是线程池,它的生命周期几乎是伴随整个服务进程的,如果Thread的map的值越来越多,就会oom了啊。 OMG,牛X,不愧是高级专家,一下子发现了这么深层的问题。

ok,那简单的呀,我提供一个remove方法吧。你get完就remove一下,不就好了。

专家:那你这个框架设计的不够好啊,还要让用户有这种心智负担,他们肯定会吐槽,并且不喜欢用的。万一落了这个remove,人家肯定会吐槽你这个框架写的不好。 那我给你个提示吧,当我们的MyThreadValueKey没有直接使用的地方时,它是不是可以被gc掉了。但是因为map的存在,导致key没法gc。我们可以用WeakReference啊。这样每次gc时,如果key没有其他引用,这样key就可以被回收。 然后每次set时,检查key为null的,直接覆盖。这样value的引用也没有了,就会被gc掉了。

我:牛X,orz。大佬就是大佬,这个设计太好了。

到这,我们把Thread的myValueMap替换为现在jdk里的threadLocals,MyThreadValueKey替换为ThreadLocal。

现在你了解ThreadLocal的设计及大家所说的内存泄漏的原因了吗? 如果不用WeakReference,才是真的内存泄漏。 根源是你要为Thread设计一个伴随Thread生命周期的私游变量池,同时线程的复用技术,导致单个线程的长生命周期,必然会导致这个私有变量池被累积,最后弄成内存泄漏。