一、ThreadLocal解密

 如果让我们自己实现ThreadLocal功能,我们第一反应想到的应该是这么做

  • 新建一个类名为MyThreadLocal
  • 在类种创建一个Map类型的成员变量,key是Thread,value是Object

但这样会有一个很严重的问题,内存泄漏。只要这个MyThreadLocal对象不回收,那Map中的对象也不会被回收,除非每次手动删除,但这样太麻烦了,一不注意就会导致OOM。所以这样设计是有很严重的缺陷的,那JDK中的ThreadLocal是如何是实现的呢?

通过观察ThreadLocal的get方法我们发现,它的ThreadLocalMap不是ThreadLocal中的变量,而是Thread中的一个内部类

java项目高并发场景 java高并发项目实战_数组

 

java项目高并发场景 java高并发项目实战_内存泄露_02

 再看一下map.getEntry具体是如何实现的

 

java项目高并发场景 java高并发项目实战_java项目高并发场景_03

ThreadLocalMap这个内部类中,有个Entry数组来存储对象,先根据ThreadLocal中的threadLocalHashCode来计算出数组的下标,从而得到Entry对象

二、ThreadLocal会内存泄露吗

我们已知,Thread中有ThreadLocalMap,ThreadLocalMap中由Entry负责存储对象,所以只要Thread能被回收,就不会有内存泄露问题,所以常规的使用是没有问题的

但如果在线程池中使用了ThreadLocal,由于线程池的线程Thread不会被回收,所以此时会有内存泄露问题吗?比如下图这种代码,虽然每个call方法里都创建了ThreadLocal,但最终是分别存储到5个Thread的ThreadLocalMap的Entry数组中的,每个数组有4个对象

java项目高并发场景 java高并发项目实战_内存泄漏_04

我们看存储对象的Entry类,key是ThreadLocal,value是Object,而key是软引用(如下图),即会被垃圾回收

java项目高并发场景 java高并发项目实战_java项目高并发场景_05

那么模拟一下过程,刚执行完成,还未触发垃圾回收时,ThreadLocalMap如下图所示

java项目高并发场景 java高并发项目实战_内存泄露_06

 触发垃圾回收后,key被回收,就变成了下图

java项目高并发场景 java高并发项目实战_java项目高并发场景_07

此时如果再触发get方法,红框2的e是null,所以会进入getEntryAfterMiss方法,在这个方法里会把value设置为null,然后就能value对应的对象给回收了

java项目高并发场景 java高并发项目实战_java_08

但只有在调用了set、get、remove方法时才能够触发,如果不再调用,还是会有内存泄露问题。所以可以在不使用的使用调用下remove方法,避免内存泄露

总结一下,是否会发生内存泄漏要看ThreadLocal这个对象是否会被回收。ThreadLocal对象作为ThreadLocalMap中的key,虽然是软引用,但如果有别的强引用在引用ThreadLocal时,即使发生GC也不会被回收。比如ThreadLocal作为类的成员变量,因为类基本不会回收,所以ThreadLocal对象也不会被回收,所以不用考虑内存泄漏问题。如果ThreadLocal是方法的局部变量,那ThreadLocal就可能被回收,就会发生内存泄漏问题

三、InheritableThreadLocal

但父线程一定要先set才行

java项目高并发场景 java高并发项目实战_数组_09

 而实现原理则是在new TestInheritableThreadLocal()这步中。因为TestInheritableThreadLocal继承Thread,所以最终会调用Thread的init方法,最关键的即是createInheritedMap方法

java项目高并发场景 java高并发项目实战_内存泄漏_10

 而该方法最终会调用ThreadLocalMap的构造方法。其中parentMap就是父线程的ThreadLocalMap对象,将parentMap中的table复制到了子线程的table中,这样就实现的对象的引用传递

java项目高并发场景 java高并发项目实战_内存泄露_11

 但由于是创建时进行了拷贝,所以要先在父线程中设置好,才能在子线程中获取对象