目录
  • threadloalmap数据结构
    • hash冲突及解决
  • threadlocal内存泄露
      • threadlocal应用场景

        源码实现

        一个线程内可以存多个threadlocal对象,存储的位置位于thread的threadlocal.threadlocalmap变量,在thread中有如下变量:

        /* threadlocal values pertaining to this thread. this map is maintained
         * by the threadlocal class. */
        threadlocal.threadlocalmap threadlocals = null;

        threadlocalmap是由threadlocal维护的静态内部类,正如代码中注解所说这个变量是由threadlocal维护的。

        基本流程

        threadloalmap数据结构

        threadloalmap是threadlocal中的一个静态内部类,类似hashmap的数据结构,但并没有实现map接口。

        static class threadlocalmap {
         
            static class entry extends weakreference<threadlocal<?>> {
                /** the value associated with this threadlocal. */
                object value;
         
                entry(threadlocal<?> k, object v) {
                    super(k);
                    value = v;
                }
            }
         
            private static final int initial_capacity = 16;
         
            // ...
        }

        threadloalmap中初始化了一个大小16的entry数组,entry对象用来保存每一个key-value键值对。通过上面的set方法,我们已经知道其中的key永远都是threadlocal对象。

        hash冲突及解决

        entry在table中存储位置是通过hashcode算法获得。
        在向threadlocalmap中的entry数值存储entry对象时,会根据threadlocal对象的hash值,定位到table中的位置i。分三种情况:

        • 如果当前位置为空的,直接将entry存放在对应位置;
        • 如果位置i已经有值且这个entry对象的key正好是即将设置的key,那么重新设置entry中的value;
        • 如果位置i的entry对象和即将设置的key没关系,则寻找一个空位置;

        计算hash值便会有hash冲突出现,常见的解决方法有:再哈希法、开放地址法、建立公共溢出区、链式地址法等。

        上面的流程可以看出这里采用的是开放地址方法,如果当前位置有值,就继续寻找下一个位置,注意table[len-1]的下一个位置是table[0],就像是一个环形数组,所以也叫闭散列法。 如果一直都找不到空位置就会出现死循环,发生内存溢出。当然有扩容机制,一般不会找不到空位置的。

        threadlocal内存泄露

        内存引用链路

        根据前面对threadlocal的分析,得知每个thread维护一个threadlocalmap,它key是threadlocal实例本身,value是业务需要存储的object。也就是说threadlocal本身并不存储值,它只是作为一个key来让线程从threadlocalmap获取value。

        threadlocalmap是使用threadlocal的弱引用作为key的,弱引用的对象在gc时会被回收。因此使用了threadlocal后,引用链如图所示:(其中虚线表示弱引用。)

        引用类型

        强引用:java默认的引用类型,例如 object a = new object();其中 a 为强引用,new object()为一个具体的对象。一个对象从根路径能找到强引用指向它,jvm虚拟机就不会回收。

        软引用(softreference):进行年轻代的垃圾回收不会触发softreference所指向对象的回收;但如果触发full gc,那softreference所指向的对象将被回收。备注:是除了软引用之外没有其他强引用引用的情况下。

        弱引用(weakreference) :如果对象除了有弱引用指向它后没有其他强引用关联它,当进行年轻代垃圾回收时,该引用指向的对象就会被垃圾回收器回收。

        虚引用(phantomereference) :该引用指向的对象,无法对垃圾收集器收集对象时产生任何影响,但在执行垃圾回收后垃圾收集器会通过注册在phantomereference上的队列来通知应用程序对象被回收。

        为什么使用弱引用而不是强引用?

        问题1:从表面上看内存泄漏的根源在于使用了弱引用,但为什么jdk采用了弱引用的实现而不是强引用呢?

        答案是:弱引用反而是为了解决内存存储问题而专门使用的。

        问题2:如果应用程序觉得threadlocal对象的使命完成,将threadlocal ref 设置为null,如果entry中引用threadlocald对象的引用类型设置为强引用的话,会发生什么问题?

        答案是:threadlocal对象会无法被垃圾回收器回收,因为从thread对象出发,有强引用指向threadlocal的object。此时会违背用户的初衷,造成所谓的内存泄露。

        我们先来假设一下,如果key使用强引用,那么在其他持有threadlocal引用的对象都回收了,但threadlocalmap依旧持有threadlocal的强引用,这就导致threadlocal不会被回收,从而导致entry内存泄露。

        对照一下,弱引用的情况。持有threadlocal引用的对象都回收了,threadlocalmap持有的是threadlocal的弱引用,会被自动回收。只不过对应的value值,需要在下次调用set/get/remove方法时会被清除。

        泄露原因分析

        当thread执行完会被销毁,thread.threadlocals指向的threadlocalmap实例也随之变为垃圾,它里面存放的entity也会被回收。这种情况是不会发生内存泄漏的。

        发生内存泄露的场景一般存在于线程池的情况下。 此时,thread生命周期比较长(存在循环使用),threadlocals引用一直存在,当其存放的threadlocal被回收(弱引用生命周期比较短)后,对应的entity就成了key为null的实例,但value值不会被回收。 如果此entity一直不被get()、set()、remove(),就一直不会被回收,也就发生了内存泄漏。

        所以,通常在使用完threadlocal后需要调用remove()方法进行内存的清除。

        接下来我们再延伸一下,想再来谈谈网络上关于threadlocalmap中存储大量entry对象导致的内存“泄露”问题?

        网络观点:在使用threadlocal中set方法与remove方法需要成对执行,需要没有执行remove方法会造成内存泄露?甚至造成内存溢出?

        我的观点:当然能成对使用当然更好,但在实际情况中,其实不调用remove方法也不太容易造成内存溢出,因为从存储结构来看,除非创建海量线程,并且这些线程都不释放,导致大量线程内部持有的threadlocalmap中对象一直不会释放,但一个线程所持有的entry对象个数不多,取决于关联的threadlocal对象个数,故我们需要的关注点而不是remove方法,而是防止线程资源泄露。

        threadlocal应用场景

        • 线程间数据隔离,各线程的threadlocal互不影响;
        • 方便同一个线程使用某一对象,避免不必要的参数传递;
        • 全链路追踪中的traceid或者流程引擎中上下文的传递一般采用threadlocal;
        • spring事务管理器采用了threadlocal;
        • spring mvc的requestcontextholder的实现使用了threadlocal;

        到此这篇关于threadlocal的基本原理的文章就介绍到这了,更多相关threadlocal原理内容请搜索www.887551.com以前的文章或继续浏览下面的相关文章希望大家以后多多支持www.887551.com!