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

in 编程
关注公众号【好便宜】( ID:haopianyi222 ),领红包啦~
阿里云,国内最大的云服务商,注册就送数千元优惠券:https://t.cn/AiQe5A0g
腾讯云,良心云,价格优惠: https://t.cn/AieHwwKl
搬瓦工,CN2 GIA 优质线路,搭梯子、海外建站推荐: https://t.cn/AieHwfX9

前言

在上周发的《求求你别用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掉就可以了。

 

结语

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

 

                                                                           

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

关注公众号【好便宜】( ID:haopianyi222 ),领红包啦~
阿里云,国内最大的云服务商,注册就送数千元优惠券:https://t.cn/AiQe5A0g
腾讯云,良心云,价格优惠: https://t.cn/AieHwwKl
搬瓦工,CN2 GIA 优质线路,搭梯子、海外建站推荐: https://t.cn/AieHwfX9
扫一扫关注公众号添加购物返利助手,领红包
Comments are closed.

推荐使用阿里云服务器

超多优惠券

服务器最低一折,一年不到100!

朕已阅去看看