ThreadLocal如何实现线程间的隔离,为什么ThreadLocal经常容易出现内存溢出。带着这两个问题,在源码中寻找答案。先从设置值开始,看看ThreadLocal.set()是如何实现保值的。publicvoidset(Tvalue){Threadt=Thread.currentThread();//获取线程私有属性threadLocalsThreadLocalMapmap=getMap(t);if(map!=null){map.set(this,value);}else{createMap(t,value);}}threadLocals:线程Thread对象的内部属性。这个属性默认是null,现在好像是通过ThreadLocal.set来初始化的。不用管ThreadLocalMap是怎么实现的,把它当做一个Map类型的容器就行了。一开始不明白线程使用的变量不是Object,而是容器。设置值的时候,为什么不直接ThreadLocalsetval直接给Thread.threadLocals而是分配一个容器。然后想一想:一个Thread+一个ThreadLocal只能保存一个val,但是一个Thread可以对应多个ThreadLocal,一个线程对象属性可能被多个ThreadLocal共享,这几乎限制了一个Thread和一个ThreadLocal的思路。我们先看看createMap是怎么初始化的,value是怎么保存的。voidcreateMap(Threadt,TfirstValue){t.threadLocals=newThreadLocalMap(this,firstValue);}ThreadLocalMap:ThreadLocal是一个内部类,名字看起来像一个Map实现类。其实本质上和Map没有关系,也没有实现Map接口。内部使用Entry(类似于Mapkey-value对象)数组存储数据,使用Hash算法计算下标。如果出现哈希冲突,如何解决?这个低调的Entry没有链表、红黑树等黑科技。staticclassThreadLocalMap{/***使用弱引用将ThreadLocal包装成一个mapKey*在某些情况下key会被回收*/staticclassEntryextendsWeakReference>{/**与此关联的值ThreadLocal.*/对象值;条目(ThreadLocal<?>k,对象v){超级(k);值=v;}}ThreadLocalMap(ThreadLocal>firstKey,ObjectfirstValue){table=newEntry[INITIAL_CAPACITY];//使用hashCode计算下标//这里使用的hashCode和我们普通的对象不一样。通过自增长计算inti=firstKey.threadLocalHashCode&(INITIAL_CAPACITY-1);table[i]=newEntry(firstKey,firstValue);大小=1;设置阈值(初始容量);}WeakReference:弱引用对象的生命周期较短。在垃圾回收线程扫描其管辖内存区域的过程中,一旦发现只有弱引用的对象,无论当前内存空间是否足够,都会回收其内存。因此,一旦出现gcThreadLocalMap键,就会被回收,导致ThreadLocal设置值无法删除,对象积压过多会导致内存溢出。现在第二个问题已经回答了,因为mapkey会被gc释放,所以value不能被删除,所以val必须在使用后手动释放。privatevoidset(ThreadLocal>key,Objectvalue){Entry[]tab=table;intlen=tab.length;inti=key.threadLocalHashCode&(len-1);//在循环中处理哈希冲突for(Entrye=tab[i];e!=null;e=tab[i=nextIndex(i,len)]){ThreadLocal>k=e.get();if(k==key){//等于直接覆盖e.value=value;返回;}if(k==null){//key已经被gc了//这个i不会改变,只能向后找一位//这里会继续向后找,直到找到符合条件的位置,key为也是通过gc值回收的,//这个只有在一定条件下才会生效replaceStaleEntry(key,value,i);返回;}}//当hash冲突时,会一直去Search,直到有空位置tab[i]=newEntry(key,value);intsz=++大小;//查找位置i之后是否有keyrecovery,然后删除数组的位置,返回true//当有删除时,不需要判断扩容情况。新增对应删除,容量不增加if(!cleanSomeSlots(i,sz)&&sz>=threshold)rehash();//展开数组}当出现hash冲突时,只需将下标向后移动,寻找空闲位置即可。正如set方法注释所说,set不支持Quickset,冲突通过向后遍历数组找空位置。ThreadLocalMap其实有一种机制,检测到key为空,删除数组中的位置。该机制只能在特定情况下触发。首先,必须在新添加的密钥后面找到回收的密钥。看ThreadLocal.get如何取值publicTget(){Threadt=Thread.currentThread();//调用get不会初始化threadLocalsThreadLocalMapmap=getMap(t);if(map!=null){ThreadLocalMap.条目e=map.getEntry(this);if(e!=null){@SuppressWarnings("unchecked")T结果=(T)e.value;返回结果;}}//如果没有获取到值,则返回nullreturnsetInitialValue();}让我们看看ThreadLocalMap内部是如何返回值的privateEntrygetEntry(ThreadLocal>key){inti=key.threadLocalHashCode&(table.length-1);条目e=表[i];//通过计算下标查找if(e!=null&&e.get()==key)returne;否则返回getEntryAfterMiss(key,i,e);}//向后查找匹配keyprivateEntrygetEntryAfterMiss(ThreadLocal>key,inti,Entrye){Entry[]tab=table;intlen=tab.length;什么ile(e!=null){//遇到null时停止ThreadLocal>k=e.get();if(k==key)返回e;if(k==null)//k已经被gc//删除数组中的这个位置,可以帮助valuerecycleexpungeStaleEntry(i);否则i=nextIndex(i,len);e=tab[i];}返回空值;}这里get不是一次性的,如果找到了,就通过反向遍历来匹配。与HashMap相比,插入和查找效率介于N之间。前两个疑惑现在已经解决了。不知道大家有没有好奇为什么ThreadLocalMap的弱引用在使用的时候不会被gc丢弃,导致传入了值却获取不到返回值。为什么要用弱引用来包裹Key,如何权衡利弊。做一个小例子publicvoidprint(){WeakReference