Java的每个Thread实例中,都有一个ThreadLocalMap类型的实例字段,它存放了该线程所用到过的所有ThreadLocal式样的实例对象,比如,有个类中声明了这个字段private ThreadLocal<Foo> threadLocalFoo = new ThreadLocal<>();,虽然它的一个实例被多个线程持有,但这些线程不一定都访问过这个实例的threadLocalFoo字段,只有访问过这个字段的Thread,它的thread local map中才会存Foo对象(以Entry的方式存,key为该ThreadLocal实例(共享),value为每个线程自己持有的Foo对象(私有))。
如果当前的Thread中的thread local map字段不空,并且其中存的有对应的对象,那么返回。
如果thread local map字段不空,但是没有存对应的对象,那么使用initialValue创建对象,然后将它和该ThreadLocal实例,打包成Entry放入当前的thread local map中,返回创建的对象。
如果thread local map字段为空,那么首先创建对象,然后创建该线程的thread local map,然后再存Entry,再返回创建的对象。
总而言之呢,get方法就是说返回的对象都必须从当前线程的thread local map中取,thread local map没创建,就创建thread local map,创建了但里面没有需要的对象,那么就创建对象并将其塞进去,反正必须从thread local map中拿就对了。
setInitialValue方法:
createMap方法:
1.1.2. set方法
Set方法,将传入的对象设置到当前的线程的thread local map中,注意,Entry的Key为set方法所在的ThreadLocal实例。
还是一样,没有thread local map就创建thread local map,反正必须塞入当前的thread local map中。
1.1.3. remove方法
remove方法,就是获取当前线程的thread local map,如果它不空的话,就移除key为remove方法所在的ThreadLocal的Entry(不同的ThreadLocal实例对应着不同的Entry,而同一个ThreadLocal实例在一个thread local map中最多存一个,但是可以存在多个thread local map中)。
这个Entry数组初始容量为16,threshold为当前数组长度的三分之二(hard code),每次向Thread local map放入entry之后,会检查更新后的size(数组中的Entry数量)是否达到了threshold,如果达到了,那么就需要进行扩容,扩容的逻辑是,先把所有stale entry清理后,判断清理的数量是否达到了四分之一threshold,如果是,那么说明当前thread local map只是因为stale entry太多的缘故导致的容量紧张,就只需执行清理动作,而不用将底层数组容量翻倍并进行entry的迁移,这个策略的目的:
谈及ThreadLocal map的时候,我们谈到了,当使用threadlocal任务不进行remove操作,并且任务又在线程池中运行时,有伪内存泄露的风险,这个风险被thread local map本身的实现抑制了,但是仍然存在,解决的办法就是即使使用remove操作。
此外还有一种更加严重的内存泄露:每个线程实例持有thread local map,然后间接持有了线程特有对象(thread local的泛型类型),在Tomcat环境下,Web应用(打包成WAR)自身定义的类由类加载器WebAppClassLoader负责加载, JDK的标准类由类加载器StandardClassLoader负责加载。不管类每个类被哪个加载器加载,它都持有了加载它的加载器的引用,除了最特殊的那个。对于WebAppClassLoader来说,它还会持有它加载过的所有class的引用,这样就导致,如果如果某个由WebAppClassLoader加载的类型(假设为ThreadLocalMemoryLeak)有个静态的ThreadLocal字段(threadLocalFoo),那么该线程特有对象(foo对象)会持有该对象的Class对象(Foo.class),Foo类型会持有WebAppClassLoader,WebAppClassLoader又会持有ThreadLocalMemoryLeak的Class对象,这个Class对象又持有了threadLocalFoo这个静态字段,也就是说,foo对象这个线程特有对象,最终又反过来持有ThreadLocal实例了,这就导致,如果不及时remove的话,那么thread local map中的Entry永远不会stale,即使这个Web app不运行了,但是Tomcat容器还在运行的话,由于底层的这些线程不会被销毁,因此thread local就产生了内存泄露,更进一步讲Foo类的Class对象、ThreadLocalMemoryLeak的Class对象,以及它们的静态变量所引用的所有对象,都无法被回收。当然Tomcat提供了一套内存泄露的检查机制以及一定程度的自动规避,但我们不要依赖这个机制。为了解决这个问题,我们要及时remove。
Java的每个Thread实例中,都有一个ThreadLocalMap类型的实例字段,它存放了该线程所用到过的所有ThreadLocal式样的实例对象,比如,有个类中声明了这个字段private ThreadLocal<Foo> threadLocalFoo = new ThreadLocal<>();,虽然它的一个实例被多个线程持有,但这些线程不一定都访问过这个实例的threadLocalFoo字段,只有访问过这个字段的Thread,它的thread local map中才会存Foo对象(以Entry的方式存,key为该ThreadLocal实例(共享),value为每个线程自己持有的Foo对象(私有))。
如果当前的Thread中的thread local map字段不空,并且其中存的有对应的对象,那么返回。
如果thread local map字段不空,但是没有存对应的对象,那么使用initialValue创建对象,然后将它和该ThreadLocal实例,打包成Entry放入当前的thread local map中,返回创建的对象。
如果thread local map字段为空,那么首先创建对象,然后创建该线程的thread local map,然后再存Entry,再返回创建的对象。
总而言之呢,get方法就是说返回的对象都必须从当前线程的thread local map中取,thread local map没创建,就创建thread local map,创建了但里面没有需要的对象,那么就创建对象并将其塞进去,反正必须从thread local map中拿就对了。
setInitialValue方法:
createMap方法:
1.1.2. set方法
Set方法,将传入的对象设置到当前的线程的thread local map中,注意,Entry的Key为set方法所在的ThreadLocal实例。
还是一样,没有thread local map就创建thread local map,反正必须塞入当前的thread local map中。
1.1.3. remove方法
remove方法,就是获取当前线程的thread local map,如果它不空的话,就移除key为remove方法所在的ThreadLocal的Entry(不同的ThreadLocal实例对应着不同的Entry,而同一个ThreadLocal实例在一个thread local map中最多存一个,但是可以存在多个thread local map中)。
这个Entry数组初始容量为16,threshold为当前数组长度的三分之二(hard code),每次向Thread local map放入entry之后,会检查更新后的size(数组中的Entry数量)是否达到了threshold,如果达到了,那么就需要进行扩容,扩容的逻辑是,先把所有stale entry清理后,判断清理的数量是否达到了四分之一threshold,如果是,那么说明当前thread local map只是因为stale entry太多的缘故导致的容量紧张,就只需执行清理动作,而不用将底层数组容量翻倍并进行entry的迁移,这个策略的目的:
谈及ThreadLocal map的时候,我们谈到了,当使用threadlocal任务不进行remove操作,并且任务又在线程池中运行时,有伪内存泄露的风险,这个风险被thread local map本身的实现抑制了,但是仍然存在,解决的办法就是即使使用remove操作。
此外还有一种更加严重的内存泄露:每个线程实例持有thread local map,然后间接持有了线程特有对象(thread local的泛型类型),在Tomcat环境下,Web应用(打包成WAR)自身定义的类由类加载器WebAppClassLoader负责加载, JDK的标准类由类加载器StandardClassLoader负责加载。不管类每个类被哪个加载器加载,它都持有了加载它的加载器的引用,除了最特殊的那个。对于WebAppClassLoader来说,它还会持有它加载过的所有class的引用,这样就导致,如果如果某个由WebAppClassLoader加载的类型(假设为ThreadLocalMemoryLeak)有个静态的ThreadLocal字段(threadLocalFoo),那么该线程特有对象(foo对象)会持有该对象的Class对象(Foo.class),Foo类型会持有WebAppClassLoader,WebAppClassLoader又会持有ThreadLocalMemoryLeak的Class对象,这个Class对象又持有了threadLocalFoo这个静态字段,也就是说,foo对象这个线程特有对象,最终又反过来持有ThreadLocal实例了,这就导致,如果不及时remove的话,那么thread local map中的Entry永远不会stale,即使这个Web app不运行了,但是Tomcat容器还在运行的话,由于底层的这些线程不会被销毁,因此thread local就产生了内存泄露,更进一步讲Foo类的Class对象、ThreadLocalMemoryLeak的Class对象,以及它们的静态变量所引用的所有对象,都无法被回收。当然Tomcat提供了一套内存泄露的检查机制以及一定程度的自动规避,但我们不要依赖这个机制。为了解决这个问题,我们要及时remove。