本文已被Github收录,推荐阅读——Seneca什么是ThreadLocal首先看ThreadLocal的使用例子:publicclassThreadLocalTest{privatestaticThreadLocalthreadLocal=newThreadLocal<>();publicstaticvoidmain(String[]args){Threadthread1=newThread(()->{threadLocal.set("局部变量1");print("thread1");System.out.println("Thevalueof线程1的局部变量是:"+threadLocal.get());});Threadthread2=newThread(()->{threadLocal.set("局部变量2");print("thread2");System.out.println("线程2的局部变量的值为:"+threadLocal。得到());});thread1.start();thread2.start();}publicstaticvoidprint(Strings){System.out.println(s+":"+threadLocal.get());}执行结果如下我们从Thread类开始,在Thread类中维护了两个ThreadLocal.ThreadLocalMap对象,分别是:threadLocals和inheritableThreadLocals。/*ThreadLocal值属于这个线程。该映射由ThreadLocal类维护*。*/ThreadLocal.ThreadLocalMapthreadLocals=null;/**InheritableThreadLocal值属于这个线程。此映射*由InheritableThreadLocal类维护。*/ThreadLocal.ThreadLocalMapinheritableThreadLocals=null;最初它们都是空的,它们只有在调用ThreadLocal类的set或get时才被创建。ThreadLocalMap可以理解为一个线程私有的HashMap。ThreadLoalMap是ThreadLocal中的静态内部类,类似于HashMap的数据结构,但没有实现Map接口。ThreadLoalMap中初始化了一个大小为16的Entry数组,Entry对象用于保存每一个key-value键值对。键是一个ThreadLocal对象。entry是用来保存数据的,也是一种继承的弱引用。使用ThreadLocal作为Entry内部的key,使用我们设置的value作为value。ThreadLocal原理set()方法当我们调用ThreadLocal的set()方法时,实际上是调用了当前线程的ThreadLocalMap的set()方法。在ThreadLocal的set()方法中,会进一步调用Thread.currentThread()获取当前线程对象,然后获取当前线程对象的ThreadLocal,判断是否为空。如果为空,先调用creadMap()创建,然后set(value)创建一个ThreadLocalMap对象,并添加变量。如果不为空,直接set(value)。这种保证线程安全的方式称为线程关闭。线程只能看到自己的ThreadLocal变量。线程是相互隔离的。get()方法get()方法用于获取与当前线程关联的ThreadLocal的值。如果当前线程没有ThreadLocal的值,则调用initialValue函数获取初始值并返回,所以一般我们在使用时都需要继承这个函数。输出初始值(不重写默认返回Null)。主要步骤如下:获取当前Thread对象,通过getMap获取Thread中的ThreadLocalMap。如果map已经存在,则以当前ThreadLocal为key获取Entry对象,从Entry中获取value。否则,调用setInitialValue进行初始化。/***返回此线程局部变量的当前线程副本中的值。如果变量对于当前线程没有值,它首先被初始化为返回的值*通过调用{@link#initialValue}方法。**@return这个线程局部的当前线程的值*/publicTget(){Threadt=Thread.currentThread();ThreadLocalMapmap=getMap(t);if(map!=null){ThreadLocalMap.Entrye=map.getEntry(this);if(e!=null){@SuppressWarnings("unchecked")T结果=(T)e.value;返回结果;}}returnsetInitialValue();}我们可以覆盖initialValue()来设置初始值。privatestaticfinalThreadLocalthreadLocal=newThreadLocal(){@OverrideprotectedIntegerinitialValue(){returnInteger.valueOf(0);remove()方法最后要探讨的是remove方法,它使用Used从地图中删除未使用的Entry。它还首先计算哈希值。如果第一次没有命中,就会一直循环直到null。这个过程中还会调用expungeStaleEntry清除空key节点。代码如下:publicvoidremove(){ThreadLocalMapm=getMap(Thread.currentThread());if(m!=null)m.remove(this);}/***删除键的条目。*/privatevoidremove(ThreadLocal>key){Entry[]tab=table;intlen=tab.length;inti=key.threadLocalHashCode&(len-1);for(Entrye=tab[i];e!=null;e=tab[i=nextIndex(i,len)]){if(e.get()==key){e.clear();expungeStaleEntry(i);返回;}}}实际上在ThreadLocalMap中使用的关键是ThreadLocal的弱引用。弱引用的特点是,如果这个对象只有弱引用,就会在下次垃圾回收时被清理掉。所以如果ThreadLocal没有被外部强引用,在垃圾回收的时候就会被清理掉,这样ThreadLocalMap中使用这个ThreadLocal的key也会被清理掉。但是value是强引用,不会被清理,所以会存在key为null的value。存在内存泄漏问题。在执行ThreadLocal的set、remove、rehash等方法时,会扫描key为null的Entry。如果发现一个Entry的key为null,说明它对应的value是无用的,所以它会把对应对象的value设置为null,这样value对象就可以正常回收了。但是假设不再使用ThreadLocal,那么set、remove、rehash方法实际上是不会被调用的。这会导致值的内存泄漏。ThreadLocal的Hash算法ThreadLocalMap与HashMap类似,有自己的Hash算法。privatefinalintthreadLocalHashCode=nextHashCode();privatestaticfinalintHASH_INCREMENT=0x61c88647;privatestaticintnextHashCode(){returnnextHashCode.getAndAdd(HASH_INCREMENT);}publicfinalintgetAndAdd(intdelta){returnIntvalueAddOf(this.,delta);}HASH_INCREMENT称为斐波那契数,也称为黄金分割数.优点是哈希分布非常均匀。每当创建ThreadLocal对象时,ThreadLocal.nextHashCode的值将增加0x61c88647。说到Hash,就会涉及到Hash冲突。不同于HashMap通过链地址方式,ThreadLocal通过线性检测方式/开放地址方式解决哈希冲突。ThreadLocal1.7和1.8的区别在ThreadLocal1.7中,入口对象的key是Thread。1.8版本的入口key是ThreadLocal。1.8版本的好处:Thread被销毁时,ThreadLocalMap也会被销毁,减少内存占用。因为ThreadLocalMap在Thread中,只要Thread消失了,ThreadLocalMap也就不复存在了。ThreadLocal问题ThreadLocal内存泄漏问题ThreadLocalMap中Entry的key是对ThreadLocal的WeakReference弱引用,而value是强引用。当ThreadLocalMap的一个ThreadLocal对象只是弱引用时,该对象会在GC发生时被清理掉。这个时候key是null,但是value是强引用的,不会被清理。此时该值将不可访问,也不会被清理,可能会造成内存泄漏。注意构造函数中的第一行代码super(k),这意味着ThreadLocal对象是一个弱引用/***这个哈希映射中的条目扩展了WeakReference,使用*它的主要引用字段作为键(它总是一个*ThreadLocal对象)。请注意,空键(即entry.get()*==null)意味着不再引用该键,因此可以从表中删除*条目。这些条目在后面的代码中被称为*作为“陈旧条目”。*/staticclassEntryextendsWeakReference>{/**与此ThreadLocal关联的值。*/对象值;条目(ThreadLocal>k,对象v){super(k);值=v;所以我们最好在使用ThreadLocal之后手动调用remove()方法。但实际上,在ThreadLocalMap的实现中,考虑到这种情况,在调用set()、get()、remove()方法时,会清理key为null的记录。为什么使用弱引用而不是强引用?为什么使用弱引用而不是强引用?评论中有这样一段话:为了辅助处理数据比较大,生命周期比较长的场景,哈希表中的entry使用WeakReference作为key。所以弱引用就是专门用来解决内存存储问题的。事实上,弱引用的使用增加了一层保护。ThreadLocal被清理后,key为null,对应的value在接下来的ThreadLocalMap调用中调用set和get。即使您忘记调用remove方法,弱引用也可以提供比强引用多一层的保护。所以,内存泄漏的根本原因在于是否手动清除操作,而不是弱引用。在ThreadLocal父子线程继承的异步场景下,父线程的线程副本数据不能共享给子线程。这个问题可以通过InheritableThreadLocal类来解决。它的原理是在父线程中调用newThread()创建子线程,在Thread构造方法中调用Thread的init方法。在init方法中,父线程的数据会被复制到子线程中(ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);)。代码示例:publicclassInheritableThreadLocalDemo{publicstaticvoidmain(String[]args){ThreadLocalthreadLocal=newThreadLocal<>();ThreadLocalinheritableThreadLocal=newInheritableThreadLocal<>();threadLocal.set("父类数据:threadLocal");inheritableThreadLocal.set("父类数据:inheritableThreadLocal");newThread(newRunnable(){@Overridepublicvoidrun(){System.out.println("子线程获取父类threadLocal数据:"+threadLocal.get());System.out.println("子线程获取父类inheritableThreadLocal数据:"+inheritableThreadLocal.get());}}).start();}}但是我们做异步处理是使用线程池,线程池会复用线程,这样就会出现问题。我们可以使用阿里巴巴的TTL来解决这个问题。https://github.com/alibaba/tr...本博客如有错误或建议,欢迎留言指正。文章持续更新中,大家可以关注公众号第一时间阅读。