首页>>后端>>java->java多线程之threadlocal的原理分析

java多线程之threadlocal的原理分析

时间:2023-11-29 本站 点击:37

jdk1.2版本就有解决多线程并发的工具类,threadlocal类本质上以空间换时间,让每一个线程拥有一份共享变量的副本,然后这样就没有多线程并发的问题,每一个线程都各自修改自己变量副本,互不影响,

ThreadLocal的属性

ThreadLocal属性非常简单就是nextHashCode, 还有一个魔数HASH_INCREMENT变量,它是每次增加的这个固定的数,就得到下一个Hash值,下面代码官方注释说明相比于连续的递增的hashcode,每次增加固定魔数,对于2的n次方幂的数组效率更优,至于为什么选取加这个魔数,这个跟斐波那契数列有关,感兴趣可以另外搜索相关资料研究

privatefinalintthreadLocalHashCode=nextHashCode();/***Thenexthashcodetobegivenout.Updatedatomically.Startsat*zero.*/privatestaticAtomicIntegernextHashCode=newAtomicInteger();/***Thedifferencebetweensuccessivelygeneratedhashcodes-turns*implicitsequentialthread-localIDsintonear-optimallyspread*multiplicativehashvaluesforpower-of-two-sizedtables.*/privatestaticfinalintHASH_INCREMENT=0x61c88647;

ThreadLocal的set方法

get set方法是了解threadLocal的核心原理的方法,

首先获取当前线程,

获取Thread的内部对象ThreadLocalMap的变量,

如果ThreadLocalMap变量为空,则创建ThreadLocalMap对象,注意这里的参数this是threadLocal的事例

如果ThreadLocalMap变量不为空,则直接设置值

publicvoidset(Tvalue){//获取当前线程Threadt=Thread.currentThread();//获取ThreadLocalMap对象ThreadLocalMapmap=getMap(t);if(map!=null){//设置值map.set(this,value);}else{//创建createMap(t,value);}}//创建ThreadLocalMap对象voidcreateMap(Threadt,TfirstValue){t.threadLocals=newThreadLocalMap(this,firstValue);}

ThreadLocalMap类成员变量

从下面可以看到ThreadLocalMap的数组的对象是Entry对象,它是继承了WeakReference这个,初始化Entry对象时,调用了父类的构造函数,也就是Entry对象中k是弱引用,而上面创建该对象时,传入的参数就是ThreadLocal对象的this指针,所以放入ThreadLocalMap中Entry对象中ThreadLocal时弱引用,

staticclassThreadLocalMap{/***TheentriesinthishashmapextendWeakReference,using*itsmainreffieldasthekey(whichisalwaysa*ThreadLocalobject).Notethatnullkeys(i.e.entry.get()*==null)meanthatthekeyisnolongerreferenced,sothe*entrycanbeexpungedfromtable.Suchentriesarereferredto*as"staleentries"inthecodethatfollows.*/staticclassEntryextendsWeakReference<ThreadLocal<?>>{/**ThevalueassociatedwiththisThreadLocal.*/Objectvalue;Entry(ThreadLocal<?>k,Objectv){super(k);value=v;}}/***Theinitialcapacity--MUSTbeapoweroftwo.*/privatestaticfinalintINITIAL_CAPACITY=16;/***Thetable,resizedasnecessary.*table.lengthMUSTalwaysbeapoweroftwo.*/privateEntry[]table;/***Thenumberofentriesinthetable.*/privateintsize=0;/***Thenextsizevalueatwhichtoresize.*/privateintthreshold;//Defaultto0

Thread和ThreadLocalMap以及ThreadLocal之间的关系如下:

从上图可以看出ThreadLocalMap中key是ThreadLocal对象,value就是保存的变量值,这两个构成一个Entry对象,设置到ThreadLocalMap中,解决冲突方法是开发地址法,即往右面偏移,而HashMap则是拉链法,这只是两种其中一点不同,其他的可以继续往下看。

首先去key的threadLocalHashCode值与(len-1)做&运算,然后得到具体落到哪一个桶上,如果产生碰撞,则通过开放地址法,index加1往后偏移一个桶地址,如果找到key等于k,则将新value替换旧值后返回。

如果查询过程中key为null,此时会调用replaceStaleEntry替换老的Entry.

如果i的位置为空,则创建一个新的Entry,放到i的位置,size加1.

调用cleanSomeSlots清除[i,size)区间一些槽位, 如果数组的长度大于threshold(即数组的2/3),则调用rehas进行扩容.

privatevoidset(ThreadLocal<?>key,Objectvalue){//Wedon'tuseafastpathaswithget()becauseitisat//leastascommontouseset()tocreatenewentriesas//itistoreplaceexistingones,inwhichcase,afast//pathwouldfailmoreoftenthannot.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;return;}if(k==null){replaceStaleEntry(key,value,i);return;}}tab[i]=newEntry(key,value);intsz=++size;if(!cleanSomeSlots(i,sz)&&sz>=threshold)rehash();}

接下来看下replaceStaleEntry是如果替换老的Entry。

首先,以上面hash地址&(len-1)得出的位置i开始, 从后往前找,找到Entry不为空,但是Entry的key为空的(这样就是会造成内存泄漏的数据),slotToExpunge标记一个index,这样[slotToExpunge,i]这个就是需要清理的索引区间。

从staleSlot的位置后面一个位置,开始从前往后遍历,如果找到这个key,我们可以将这个Entry交换到staleSlot的位置,

如果脏数据的开始位置和slotToExpunge的索位置相等,则slotToExpunge索引赋值为从上一步从前往后找到第一个key相等的位置的索引.然后调用expungeStaleEntry从staleSlot开始清除脏数据,最后调用cleanSomeSlots启发式扫描清除某些槽位。

如果没有清理的槽位,并且size大于threshod(即size的2/3).则进行rehash进行扫描全部数组进行清理过期数据,如果还是threshod的3/4,则通过resize进行扩容。

privatevoidreplaceStaleEntry(ThreadLocal<?>key,Objectvalue,intstaleSlot){Entry[]tab=table;intlen=tab.length;Entrye;//Backuptocheckforpriorstaleentryincurrentrun.//Wecleanoutwholerunsatatimetoavoidcontinual//incrementalrehashingduetogarbagecollectorfreeing//uprefsinbunches(i.e.,wheneverthecollectorruns).intslotToExpunge=staleSlot;for(inti=prevIndex(staleSlot,len);(e=tab[i])!=null;i=prevIndex(i,len))if(e.get()==null)slotToExpunge=i;//Findeitherthekeyortrailingnullslotofrun,whichever//occursfirstfor(inti=nextIndex(staleSlot,len);(e=tab[i])!=null;i=nextIndex(i,len)){ThreadLocal<?>k=e.get();//Ifwefindkey,thenweneedtoswapit//withthestaleentrytomaintainhashtableorder.//Thenewlystaleslot,oranyotherstaleslot//encounteredaboveit,canthenbesenttoexpungeStaleEntry//toremoveorrehashalloftheotherentriesinrun.if(k==key){//交换位置e.value=value;tab[i]=tab[staleSlot];tab[staleSlot]=e;//Startexpungeatprecedingstaleentryifitexistsif(slotToExpunge==staleSlot)slotToExpunge=i;cleanSomeSlots(expungeStaleEntry(slotToExpunge),len);return;}//Ifwedidn'tfindstaleentryonbackwardscan,the//firststaleentryseenwhilescanningforkeyisthe//firststillpresentintherun.if(k==null&&slotToExpunge==staleSlot)slotToExpunge=i;}//Ifkeynotfound,putnewentryinstaleslottab[staleSlot].value=null;tab[staleSlot]=newEntry(key,value);//Ifthereareanyotherstaleentriesinrun,expungethemif(slotToExpunge!=staleSlot)cleanSomeSlots(expungeStaleEntry(slotToExpunge),len);}

staleSlot是已知key为空的Entry的索引, 从staleSlot开始从前往后搜索,

如果key为空,则设置清设置Entry的value为null。并且设置对应位置为空,如果

如果key不为空,并且i位置和hash&(len-1)不相等,说明这是通过开发地址法放进来的元素,则通过rehash,循环直到找一个hash&(len-1)的位置为空,并把它放到这个位置(这里这这一步主要是解决减少hash碰撞产生,使得查询时间复杂度为 O(1)).

privateintexpungeStaleEntry(intstaleSlot){Entry[]tab=table;intlen=tab.length;//expungeentryatstaleSlottab[staleSlot].value=null;tab[staleSlot]=null;size--;//RehashuntilweencounternullEntrye;inti;for(i=nextIndex(staleSlot,len);(e=tab[i])!=null;i=nextIndex(i,len)){ThreadLocal<?>k=e.get();if(k==null){e.value=null;tab[i]=null;size--;}else{inth=k.threadLocalHashCode&(len-1);if(h!=i){tab[i]=null;//UnlikeKnuth6.4AlgorithmR,wemustscanuntil//nullbecausemultipleentriescouldhavebeenstale.while(tab[h]!=null)h=nextIndex(h,len);tab[h]=e;}}}returni;}

这里就是搜素索引i(即expungeStaleEntry函数返回索引的位置,即staleSlot位置后第一个Entry为空的位置,即不是脏数据的索引),n就是tab的length,从i开始往后遍历,如果Entry不为空,但是key为空,保存len,赋值removed变量为true,然后调用expungeStaleEntry再次清除脏数据,然后将n缩减一半的长度,重新探索,直至0为止。

privatebooleancleanSomeSlots(inti,intn){booleanremoved=false;Entry[]tab=table;intlen=tab.length;do{i=nextIndex(i,len);Entrye=tab[i];if(e!=null&&e.get()==null){n=len;removed=true;i=expungeStaleEntry(i);}}while((n>>>=1)!=0);returnremoved;}

扫描全部数据进行清理过期数据,

size 大于 threshod的3/4,则调用resize进行扩容。

privatevoidrehash(){expungeStaleEntries();//Uselowerthresholdfordoublingtoavoidhysteresisif(size>=threshold-threshold/4)resize();}

ThreadLocal resize进行扩容

ThreadLocal的扩容机制,是将申请原先长度乘以2的数组,然后重新计算hash值,然后放入新的数组中即可,然后重新计算扩容阈值。

privatevoidresize(){Entry[]oldTab=table;intoldLen=oldTab.length;intnewLen=oldLen*2;Entry[]newTab=newEntry[newLen];intcount=0;for(intj=0;j<oldLen;++j){Entrye=oldTab[j];if(e!=null){ThreadLocal<?>k=e.get();if(k==null){e.value=null;//HelptheGC}else{inth=k.threadLocalHashCode&(newLen-1);while(newTab[h]!=null)h=nextIndex(h,newLen);newTab[h]=e;count++;}}}setThreshold(newLen);size=count;table=newTab;}

ThreadLocal get进行获取值

首先获取Thread的threadLocalMap对象,然后通过getEntry方法获Entry对象,

然后获取Entry不为空,则直接返回,

publicTget(){Threadt=Thread.currentThread();ThreadLocalMapmap=getMap(t);if(map!=null){ThreadLocalMap.Entrye=map.getEntry(this);if(e!=null){@SuppressWarnings("unchecked")Tresult=(T)e.value;returnresult;}}returnsetInitialValue();}

*首先通过key的threadLocalHashCode&(len-1),定位到对应的桶,

如果key相等,说明已经找到该元素,直接返回

如果key不等,则开发地址法,调用getEntryAfterMiss继续往查找。

publicvoidset(Tvalue){//获取当前线程Threadt=Thread.currentThread();//获取ThreadLocalMap对象ThreadLocalMapmap=getMap(t);if(map!=null){//设置值map.set(this,value);}else{//创建createMap(t,value);}}//创建ThreadLocalMap对象voidcreateMap(Threadt,TfirstValue){t.threadLocals=newThreadLocalMap(this,firstValue);}0

继续从i的位置往后搜索直到entry为空时停止

如果遇到k为空null时,则expungeStaleEntry进行清理过期数据

如果循环结束还咩有找到,则直接返回null结束

publicvoidset(Tvalue){//获取当前线程Threadt=Thread.currentThread();//获取ThreadLocalMap对象ThreadLocalMapmap=getMap(t);if(map!=null){//设置值map.set(this,value);}else{//创建createMap(t,value);}}//创建ThreadLocalMap对象voidcreateMap(Threadt,TfirstValue){t.threadLocals=newThreadLocalMap(this,firstValue);}1

ThreadLocal的remove删除值

首先还是通过threadLocalHashCode&(len-1)定位对应的数组索引i,

*从i开始从前往后搜索,知道key相等,然后删除Entry,然后执行expungeStaleEntry进行一次过期数据的清理结束

publicvoidset(Tvalue){//获取当前线程Threadt=Thread.currentThread();//获取ThreadLocalMap对象ThreadLocalMapmap=getMap(t);if(map!=null){//设置值map.set(this,value);}else{//创建createMap(t,value);}}//创建ThreadLocalMap对象voidcreateMap(Threadt,TfirstValue){t.threadLocals=newThreadLocalMap(this,firstValue);}2

总结

今天主要是对ThreadLocal的set get remove等重要方法进行一个详细的分析,也对ThreadLocal的解决Hash冲突的方法,key的弱引用,扩容、探测清理和启发式清理过期元素有了一个清理的认识。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:/java/37.html