一、ThreadLocal解密
如果让我们自己实现ThreadLocal功能,我们第一反应想到的应该是这么做
- 新建一个类名为MyThreadLocal
- 在类种创建一个Map类型的成员变量,key是Thread,value是Object
但这样会有一个很严重的问题,内存泄漏。只要这个MyThreadLocal对象不回收,那Map中的对象也不会被回收,除非每次手动删除,但这样太麻烦了,一不注意就会导致OOM。所以这样设计是有很严重的缺陷的,那JDK中的ThreadLocal是如何是实现的呢?
通过观察ThreadLocal的get方法我们发现,它的ThreadLocalMap不是ThreadLocal中的变量,而是Thread中的一个内部类
再看一下map.getEntry具体是如何实现的
如上图所示,在ThreadLocalMap这个内部类中,有个Entry数组来存储对象,先根据ThreadLocal中的threadLocalHashCode来计算出数组的下标,从而得到Entry对象
二、ThreadLocal会内存泄露吗
我们已知,Thread中有ThreadLocalMap,ThreadLocalMap中由Entry负责存储对象,所以只要Thread能被回收,就不会有内存泄露问题,所以常规的使用是没有问题的
但如果在线程池中使用了ThreadLocal,由于线程池的线程Thread不会被回收,所以此时会有内存泄露问题吗?比如下图这种代码,虽然每个call方法里都创建了ThreadLocal,但最终是分别存储到5个Thread的ThreadLocalMap的Entry数组中的,每个数组有4个对象
我们看存储对象的Entry类,key是ThreadLocal,value是Object,而key是软引用(如下图),即会被垃圾回收
那么模拟一下过程,刚执行完成,还未触发垃圾回收时,ThreadLocalMap如下图所示
触发垃圾回收后,key被回收,就变成了下图
此时如果再触发get方法,红框2的e是null,所以会进入getEntryAfterMiss方法,在这个方法里会把value设置为null,然后就能value对应的对象给回收了
但只有在调用了set、get、remove方法时才能够触发,如果不再调用,还是会有内存泄露问题。所以可以在不使用的使用调用下remove方法,避免内存泄露
总结一下,是否会发生内存泄漏要看ThreadLocal这个对象是否会被回收。ThreadLocal对象作为ThreadLocalMap中的key,虽然是软引用,但如果有别的强引用在引用ThreadLocal时,即使发生GC也不会被回收。比如ThreadLocal作为类的成员变量,因为类基本不会回收,所以ThreadLocal对象也不会被回收,所以不用考虑内存泄漏问题。如果ThreadLocal是方法的局部变量,那ThreadLocal就可能被回收,就会发生内存泄漏问题
三、InheritableThreadLocal
ThreadLocal可以实现当前线程间的对象传递,而InheritableThreadLocal则可以让子线程在创建时继承父线程中的对象。如下图代码所示,run方法里可以输出值,但父线程一定要先set才行
而实现原理则是在new TestInheritableThreadLocal()这步中。因为TestInheritableThreadLocal继承Thread,所以最终会调用Thread的init方法,最关键的即是createInheritedMap方法
而该方法最终会调用ThreadLocalMap的构造方法。其中parentMap就是父线程的ThreadLocalMap对象,将parentMap中的table复制到了子线程的table中,这样就实现的对象的引用传递
但由于是创建时进行了拷贝,所以要先在父线程中设置好,才能在子线程中获取对象