您现在的位置是:首页 > 文章详情

惊!原来你是会内存泄漏的ThreadLocal

日期:2019-04-02点击:501

前言

在上周发的《求求你别用SimpleDateFormat了!》这篇文章中,有简单的提过在ThreadLocal的使用过程中不规范的话,可能会有坑,所以我们今天就来探讨一下有哪些坑需要我们在开发的过程中注意的。

 

正文

 

基本使用

ThreadLocal可以让你创建只能在自己线程里面读写的变量,也就是说,在同一个TreadLocal变量里面,不同的线程存取的变量只能自己看到,其他线程是访问不了的。

 

相关操作

//创建对象ThreadLocal threadLocal = new ThreadLocal<String>();//设值threadLocal.set("深夜里的程序猿");//取值threadLocal.get(); // get  深夜里的程序猿

 

原理

threadlocal相关的操作还是比较简单的,那我们也来简单了解下它的实现原理,直接上源码。

 

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

 

public T get() { Thread t = Thread.currentThread(); 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 getMap(Thread t) { return t.threadLocals;}

 

可以看到源码也是比较简洁,拿set()来说,先是获取当前的运行的线程,然后通过getMap()取出当前线程的threadLocals变量,它是一个ThreadLocalMap,接着对这个map进行赋值,key是threadlocal,value是存进来的值。

 

这里要提醒一下对源码比较生疏的同学,我们存进ThreadLocal的值,其实最后是存在了对应线程的ThreadLocalMap变量里面,并非在Threadlocal自己的变量里,大家可以结合源码跟上段的文字理解一下。

 

为啥会有坑?

那么问题来了,这一切看起来都很正常,那“内存泄漏”会出现在哪里呢?还是直接看源码。​​​​​​​

static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value;  Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }

 

在这个实际存储内容的ThreadLocalMap类中中,使用了一个叫做Entry的静态内部类,它是针对于key也就是Threadlocal而存在的,对于Threadlocal来说是一个弱引用(关于强软弱虚引用有必要了解)。这意味这当ThreadLocal变量没有了强依赖以后,它会被GC回收掉,但是此时value不是弱引用,因此它还存在一个当前线程对它的引用,如果当前线程一直存活或者没调用threadlocal的remove方法的话。这个值将不会被销毁,这就发生了“内存泄漏”。(这段话需要细细品味)

那么什么时候会出现线程一直存在的情况呢?其实当我们在使用线程池的时候就是这样,为了避免频繁创建销毁线程,提高效率,线程池会重复利用一批线程,这就给我们使用不当的ThreadLocal埋下了炸弹。

 

那我们该如何避免呢?​​​​​​​

private void remove(ThreadLocal<?> key) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { if (e.get() == key) { //清理对ThreadLocal的弱引用 e.clear(); //清理key为null的元素 expungeStaleEntry(i); return; } } }

 

其实也很简单,在上面的源码中已经注释了,remove方法会对一些“无用的数据”进行清理,所以我们要养成习惯,当使用完threadlocal变量以后,及时remove掉就可以了。

 

结语

由此我们需要注意到,如果一个类的使用不当,随着时间的推移可能造成的后果是灾难性的,所以我们在用的过程中要注重一些“最佳实践”,勿“偷懒”。

 

                                                                           

喜欢的话,麻烦大家点个赞~关注一下微信公众号《深夜里的程序猿》,每天分享最干的干货

原文链接:https://my.oschina.net/19921228/blog/3031156
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章