当前位置: 首页 > 后端技术 > Java

多线程利器ThreadLocal

时间:2023-04-01 15:48:49 Java

一、示例及原理//成员变量ThreadLocalTL=newThreadLocal<>();@TestpublicvoidtestTL(){TL.set(newObject());TL.get();}ThreadLocal采用了以空间换时间的思想(变量在不同的线程中创建)来解决并发问题。该线程持有一个名为threadLocals的引用,它指向一个ThreadLocalMap。ThreadLocalMap的本质是一个Entry对象的数组。ThreadLocalMap解决hash冲突的方式和HashMap(chain,tree)不同。ThreadLocalMap会从bucket的位置开始依次搜索。例如:哈希计算的余数计算出落点桶为5,但是位置5已经有另一个条目,那么它会尝试将其放入桶6......关键是ThreadLocal对象本身,并且Entry对象以弱引用方式指向键。方法是帮助回收,防止内存溢出(源码中有扫描逻辑)。如果持有ThreadLocal的对象被回收了(样本中的成员变量不存在了),说明指向ThreadLocalMap的key的强引用不存在了,弱引用在被扫描时也会被回收GC。value是要存储的对象。2.set()publicvoidset(Tvalue){Threadt=Thread.currentThread();//获取线程的threadLocals变量ThreadLocalMapmap=getMap(t);if(map!=null)//==2.在Entry数组中找到条目,并赋值map.set(this,value);else//==1.创建一个ThreadLocalMap,并赋值//-线程有一个threadLocals变量,它指向EntryArray(一个Thread关联的多个ThreadLocal对象,默认16);//-Entry对象是弱引用createMap(t,value);}1.创建ThreadLocalMapjava.lang.ThreadLocal#createMapvoidcreateMap(Threadt,TfirstValue){//##调用ThreadLocalMap的构造函数,使用当前的threadLocal作为key//threadLocals变量最终赋值给线程t.threadLocals=newThreadLocalMap(this,firstValue);}//ThreadLocalMap的构造函数ThreadLocalMap(ThreadLocalfirstKey,ObjectfirstValue){table=newEntry[初始容量];inti=firstKey.threadLocalHashCode&(INITIAL_CAPACITY-1);//构建弱引用数组table[i]=newEntry(firstKey,firstValue);大小=1;//扩展因子,大小*2/3setThreshold(INITIAL_CAPACITY);}keyclasses,attributes#线程类ThreadLocal.ThreadLocalMapthreadLocals=null;#ThreadLocal类staticclassThreadLocalMap{privateEntry[]table;//表的大小,即本线程对应的ThreadLocals个数privateintsize=0;//弱引用:一旦发现只有弱引用的对象,无论当前内存空间是否足够,都会回收其内存。//如果不使用弱引用,那么当强引用持有的值被释放时,当线程没有被回收释放时,//threadLocalMap会一直持有ThreadLocal和值的强申请,导致该值没有被回收,导致内存泄漏staticclassEntryextendsWeakReference>{对象值;条目(ThreadLocal<?>k,对象v){超级(k);值=v;}}}2.ThreadLocalMap#setprivatevoidset(ThreadLocalkey,Objectvalue){Entry[]tab=table;intlen=tab.length;//根据hash值和len查找位置inti=key.threadLocalHashCode&(len-1);//Linearsearchfor(Entrye=tab[i];e!=null;//在循环中做线性搜索,点i在数组中移动(如果已经是数组的最后一个位置,则会从位置0继续查找)e=tab[i=nextIndex(i,len)]){//获取当前位置的key->ThreadLocalThreadLocalk=e.get();//==1.1,在Entry数组中找到对应的ThreadLocal,赋值替换if(k==key){e.value=value;返回;}//##2.原位置的条目为空(stale-invalid),替换if(k==null){replaceStaleEntry(key,value,i);返回;}}//==1.2,恩如果在try数组中没有找到,则新建一个Entry,放入空槽i(经过上面循环后,i为最近的空位)位置tab[i]=newEntry(key,value);intsz=++大小;//-A。满足扩容条件(condition:unabletoclean&&reachthethreshold),做扩容操作if(!cleanSomeSlots(i,sz)&&sz>=threshold)//--b,扩容rehash();}a,bucket清理privatebooleancleanSomeSlots(inti,intn){booleanremoved=false;条目[]标签=表格;intlen=tab.length;做{i=nextIndex(i,len);条目e=tab[i];//入口不为空,但入口的key为空(##弱引用被回收,前提是持有ThreadLocal的对象被回收,见下图)if(e!=null&&e.get()==null){n=len;//执行清理操作,返回值设置为trueremoved=true;//==清除无效条目i=expungeStaleEntry(i);}}//通过size(Entry对象的实际个数,即线程持有的ThreadLocals个数)二进制右移确定循环次数//例:2->0循环2次;8->0循环4次while((n>>>=1)!=0);returnremoved;}看看实际清理方法privateintexpungeStaleEntry(intstaleSlot){Entry[]tab=table;整数长度=制表符长度;//清除当前无效节点tab[staleSlot].value=null;tab[staleSlot]=null;尺寸-;条目e;诠释我;对于(i=nextIndex(staleSlot,len);(e=tab[i])!=null;i=nextIndex(i,len)){ThreadLocalk=e.get();//--帮助清理其他无效节点if(k==null){e.value=null;选项卡[i]=空;尺寸-;}//--帮助k找到实际的bucket//本来在位置i,无奈选择位置i——hash冲突,实际是h位置;//现在有机会把它丢到它应该在的h位置,i位置清空else{inth=k.threadLocalHashCode&(len-1);如果(h!=i){tab[i]=null;//那么实际的落点h也被占用了,只能继续寻找新的h位置while(tab[h]!=null)h=nextIndex(h,len);选项卡[h]=e;}}}returni;}b,rehash()privatevoidrehash(){//实际清理方法已经分析过expungeStaleEntries();//使用低阈值保持加倍以避免滞后if(size>=threshold-threshold/4)//expansionresize();}##expansion&&oldbucketobjectmovedtonewbucketprivatevoidresize(){Entry[]oldTab=table;intoldLen=oldTab.length;//2倍扩展intnewLen=oldLen*2;条目[]newTab=新条目[newLen];整数计数=0;for(intj=0;jk=e.get();if(k==null){e.value=null;//帮助GC}else{//计算位置inth=k.threadLocalHashCode&(newLen-1);//hash冲突,找最近的空位while(newTab[h]!=null)h=nextIndex(h,newLen);newTab[h]=e;计数++;}}}setThreshold(newLen);大小=计数;table=newTab;}原来位置的entry为空(stale-invalid),替换privatevoidreplaceStaleEntry(ThreadLocalkey,Objectvalue,intstaleSlot){条目[]tab=table;intlen=tab.length;条目e;//向左寻找一波(与其他方向相反,根据我的理解清理更全面)intslotToExpunge=staleSlot;对于(inti=prevIndex(staleSlot,len);(e=tab[i])!=null;i=prevIndex(i,len))if(e.get()==null)slotToExpunge=i;//其他的就不分析了(偷懒):好像调用了之前分析过的各种方法,总之保证扫描全面for(inti=nextIndex(staleSlot,len);(e=tab[i])!=null;i=nextIndex(i,len)){ThreadLocalk=e.get();if(k==key){e.value=value;tab[i]=tab[staleSlot];tab[staleSlot]=e;//如果前面的陈旧条目存在,则开始删除if(slotToExpunge==staleSlot)slotToExpunge=i;cleanSomeSlots(expungeStaleEntry(slotToExpunge),len);返回;}if(k==null&&slotToExpunge==staleSlot)slotToExpunge=i;}//如果找不到键,将新条目放入陈旧槽tab[staleSlot].value=null;tab[staleSlot]=newEntry(key,value);//如果运行中有任何其他陈旧条目,则将它们删除if(slotToExpunge!=staleSlot)cleanSomeSlots(expungeStaleEntry(slotToExpunge),len);}三、get()publicTget(){Threadt=Thread.currentThread();ThreadLocalMapmap=getMap(t);if(map!=null){//==获取入口点从set的逻辑推断——一定有在Entry环上查找的逻辑ThreadLocalMap.Entrye=map.getEntry(this);if(e!=null){@SuppressWarnings("unchecked")Tresult=(T)e.value;返回结果;}}returnsetInitialValue();}java.lang.ThreadLocal.ThreadLocalMap#getEntryprivateEntrygetEntry(ThreadLocalkey){inti=key.threadLocalHashCode&(table.length-1);条目e=表[i];//--查找直接返回if(e!=null&&e.get()==key)returne;//--未找到,进行线性搜索elsereturngetEntryAfterMiss(key,i,e);}java.lang.ThreadLocal.ThreadLocalMap#getEntryAfterMissprivateEntrygetEntryAfterMiss(ThreadLocalkey,inti,Entrye){Entry[]选项卡=表格;intlen=tab.length;while(e!=null){ThreadLocalk=e.get();if(k==key)返回e;//帮助清理if(k==null)expungeStaleEntry(i);//线性搜索elsei=nextIndex(i,len);e=tab[i];}返回空值;}