前言
我們都知道,在多線程環(huán)境下訪問(wèn)同一個(gè)共享變量,可能會(huì)出現(xiàn)線程安全的問(wèn)題,為了保證線程安全,我們往往會(huì)在訪問(wèn)這個(gè)共享變量的時(shí)候加鎖,以達(dá)到同步的效果,如下圖所示。
對(duì)共享變量加鎖雖然能夠保證線程的安全,但是卻增加了開(kāi)發(fā)人員對(duì)鎖的使用技能,如果鎖使用不當(dāng),則會(huì)導(dǎo)致死鎖的問(wèn)題。而ThreadLocal能夠做到在創(chuàng)建變量后,每個(gè)線程對(duì)變量訪問(wèn)時(shí)訪問(wèn)的是線程自己的本地變量。
什么是ThreadLocal?
ThreadLocal是JDK提供的,支持線程本地變量。也就是說(shuō),如果我們創(chuàng)建了一個(gè)ThreadLocal變量,則訪問(wèn)這個(gè)變量的每個(gè)線程都會(huì)有這個(gè)變量的一個(gè)本地副本。如果多個(gè)線程同時(shí)對(duì)這個(gè)變量進(jìn)行讀寫(xiě)操作時(shí),實(shí)際上操作的是線程自己本地內(nèi)存中的變量,從而避免了線程安全的問(wèn)題。
ThreadLocal使用示例
例如,我們使用ThreadLocal保存并打印相關(guān)的變量信息,程序如下所示。
publicclassThreadLocalTest{ privatestaticThreadLocalthreadLocal=newThreadLocal (); publicstaticvoidmain(String[]args){ //創(chuàng)建第一個(gè)線程 ThreadthreadA=newThread(()->{ threadLocal.set("ThreadA:"+Thread.currentThread().getName()); System.out.println("線程A本地變量中的值為:"+threadLocal.get()); }); //創(chuàng)建第二個(gè)線程 ThreadthreadB=newThread(()->{ threadLocal.set("ThreadB:"+Thread.currentThread().getName()); System.out.println("線程B本地變量中的值為:"+threadLocal.get()); }); //啟動(dòng)線程A和線程B threadA.start(); threadB.start(); } }
運(yùn)行程序,打印的結(jié)果信息如下所示。
線程A本地變量中的值為:ThreadA:Thread-0 線程B本地變量中的值為:ThreadB:Thread-1
此時(shí),我們?yōu)榫€程A增加刪除ThreadLocal中的變量的操作,如下所示。
publicclassThreadLocalTest{ privatestaticThreadLocalthreadLocal=newThreadLocal (); publicstaticvoidmain(String[]args){ //創(chuàng)建第一個(gè)線程 ThreadthreadA=newThread(()->{ threadLocal.set("ThreadA:"+Thread.currentThread().getName()); System.out.println("線程A本地變量中的值為:"+threadLocal.get()); threadLocal.remove(); System.out.println("線程A刪除本地變量后ThreadLocal中的值為:"+threadLocal.get()); }); //創(chuàng)建第二個(gè)線程 ThreadthreadB=newThread(()->{ threadLocal.set("ThreadB:"+Thread.currentThread().getName()); System.out.println("線程B本地變量中的值為:"+threadLocal.get()); System.out.println("線程B沒(méi)有刪除本地變量:"+threadLocal.get()); }); //啟動(dòng)線程A和線程B threadA.start(); threadB.start(); } }
此時(shí)的運(yùn)行結(jié)果如下所示。
線程A本地變量中的值為:ThreadA:Thread-0 線程B本地變量中的值為:ThreadB:Thread-1 線程B沒(méi)有刪除本地變量:ThreadB:Thread-1 線程A刪除本地變量后ThreadLocal中的值為:null
通過(guò)上述程序我們可以看出,線程A和線程B存儲(chǔ)在ThreadLocal中的變量互不干擾,線程A存儲(chǔ)的變量只能由線程A訪問(wèn),線程B存儲(chǔ)的變量只能由線程B訪問(wèn)。
ThreadLocal原理
首先,我們看下Thread類(lèi)的源碼,如下所示。
publicclassThreadimplementsRunnable{ /***********省略N行代碼*************/ ThreadLocal.ThreadLocalMapthreadLocals=null; ThreadLocal.ThreadLocalMapinheritableThreadLocals=null; /***********省略N行代碼*************/ }
由Thread類(lèi)的源碼可以看出,在ThreadLocal類(lèi)中存在成員變量threadLocals和inheritableThreadLocals,這兩個(gè)成員變量都是ThreadLocalMap類(lèi)型的變量,而且二者的初始值都為null。只有當(dāng)前線程第一次調(diào)用ThreadLocal的set()方法或者get()方法時(shí)才會(huì)實(shí)例化變量。
這里需要注意的是:每個(gè)線程的本地變量不是存放在ThreadLocal實(shí)例里面的,而是存放在調(diào)用線程的threadLocals變量里面的。也就是說(shuō),調(diào)用ThreadLocal的set()方法存儲(chǔ)的本地變量是存放在具體線程的內(nèi)存空間中的,而ThreadLocal類(lèi)只是提供了set()和get()方法來(lái)存儲(chǔ)和讀取本地變量的值,當(dāng)調(diào)用ThreadLocal類(lèi)的set()方法時(shí),把要存儲(chǔ)的值放入調(diào)用線程的threadLocals中存儲(chǔ)起來(lái),當(dāng)調(diào)用ThreadLocal類(lèi)的get()方法時(shí),從當(dāng)前線程的threadLocals變量中將存儲(chǔ)的值取出來(lái)。
接下來(lái),我們分析下ThreadLocal類(lèi)的set()、get()和remove()方法的實(shí)現(xiàn)邏輯。
set()方法
set()方法的源代碼如下所示。
publicvoidset(Tvalue){ //獲取當(dāng)前線程 Threadt=Thread.currentThread(); //以當(dāng)前線程為Key,獲取ThreadLocalMap對(duì)象 ThreadLocalMapmap=getMap(t); //獲取的ThreadLocalMap對(duì)象不為空 if(map!=null) //設(shè)置value的值 map.set(this,value); else //獲取的ThreadLocalMap對(duì)象為空,創(chuàng)建Thread類(lèi)中的threadLocals變量 createMap(t,value); }
在set()方法中,首先獲取調(diào)用set()方法的線程,接下來(lái),使用當(dāng)前線程作為Key調(diào)用getMap(t)方法來(lái)獲取ThreadLocalMap對(duì)象,getMap(Thread t)的方法源碼如下所示。
ThreadLocalMapgetMap(Threadt){ returnt.threadLocals; }
可以看到,getMap(Thread t)方法獲取的是線程變量自身的threadLocals成員變量。
在set()方法中,如果調(diào)用getMap(t)方法返回的對(duì)象不為空,則把value值設(shè)置到Thread類(lèi)的threadLocals成員變量中,而傳遞的key為當(dāng)前ThreadLocal的this對(duì)象,value就是通過(guò)set()方法傳遞的值。
如果調(diào)用getMap(t)方法返回的對(duì)象為空,則程序調(diào)用createMap(t, value)方法來(lái)實(shí)例化Thread類(lèi)的threadLocals成員變量。
voidcreateMap(Threadt,TfirstValue){ t.threadLocals=newThreadLocalMap(this,firstValue); }
也就是創(chuàng)建當(dāng)前線程的threadLocals變量。
get()方法
get()方法的源代碼如下所示。
publicTget(){ //獲取當(dāng)前線程 Threadt=Thread.currentThread(); //獲取當(dāng)前線程的threadLocals成員變量 ThreadLocalMapmap=getMap(t); //獲取的threadLocals變量不為空 if(map!=null){ //返回本地變量對(duì)應(yīng)的值 ThreadLocalMap.Entrye=map.getEntry(this); if(e!=null){ @SuppressWarnings("unchecked") Tresult=(T)e.value; returnresult; } } //初始化threadLocals成員變量的值 returnsetInitialValue(); }
通過(guò)當(dāng)前線程來(lái)獲取threadLocals成員變量,如果threadLocals成員變量不為空,則直接返回當(dāng)前線程綁定的本地變量,否則調(diào)用setInitialValue()方法初始化threadLocals成員變量的值。
privateTsetInitialValue(){ //調(diào)用初始化Value的方法 Tvalue=initialValue(); Threadt=Thread.currentThread(); //根據(jù)當(dāng)前線程獲取threadLocals成員變量 ThreadLocalMapmap=getMap(t); if(map!=null) //threadLocals不為空,則設(shè)置value值 map.set(this,value); else //threadLocals為空,創(chuàng)建threadLocals變量 createMap(t,value); returnvalue; }
其中,initialValue()方法的源碼如下所示。
protectedTinitialValue(){ returnnull; }
通過(guò)initialValue()方法的源碼可以看出,這個(gè)方法可以由子類(lèi)覆寫(xiě),在ThreadLocal類(lèi)中,這個(gè)方法直接返回null。
remove()方法
remove()方法的源代碼如下所示。
publicvoidremove(){ //根據(jù)當(dāng)前線程獲取threadLocals成員變量 ThreadLocalMapm=getMap(Thread.currentThread()); if(m!=null) //threadLocals成員變量不為空,則移除value值 m.remove(this); }
remove()方法的實(shí)現(xiàn)比較簡(jiǎn)單,首先根據(jù)當(dāng)前線程獲取threadLocals成員變量,不為空,則直接移除value的值。
注意:如果調(diào)用線程一致不終止,則本地變量會(huì)一直存放在調(diào)用線程的threadLocals成員變量中,所以,如果不需要使用本地變量時(shí),可以通過(guò)調(diào)用ThreadLocal的remove()方法,將本地變量從當(dāng)前線程的threadLocals成員變量中刪除,以免出現(xiàn)內(nèi)存溢出的問(wèn)題。
ThreadLocal變量不具有傳遞性
使用ThreadLocal存儲(chǔ)本地變量不具有傳遞性,也就是說(shuō),同一個(gè)ThreadLocal在父線程中設(shè)置值后,在子線程中是無(wú)法獲取到這個(gè)值的,這個(gè)現(xiàn)象說(shuō)明ThreadLocal中存儲(chǔ)的本地變量不具有傳遞性。
接下來(lái),我們來(lái)看一段代碼,如下所示。
publicclassThreadLocalTest{ privatestaticThreadLocalthreadLocal=newThreadLocal (); publicstaticvoidmain(String[]args){ //在主線程中設(shè)置值 threadLocal.set("ThreadLocalTest"); //在子線程中獲取值 Threadthread=newThread(newRunnable(){ @Override publicvoidrun(){ System.out.println("子線程獲取值:"+threadLocal.get()); } }); //啟動(dòng)子線程 thread.start(); //在主線程中獲取值 System.out.println("主線程獲取值:"+threadLocal.get()); } }
運(yùn)行這段代碼輸出的結(jié)果信息如下所示。
主線程獲取值:ThreadLocalTest 子線程獲取值:null
通過(guò)上述程序,我們可以看出在主線程中向ThreadLocal設(shè)置值后,在子線程中是無(wú)法獲取到這個(gè)值的。那有沒(méi)有辦法在子線程中獲取到主線程設(shè)置的值呢?此時(shí),我們可以使用InheritableThreadLocal來(lái)解決這個(gè)問(wèn)題。
InheritableThreadLocal使用示例
InheritableThreadLocal類(lèi)繼承自ThreadLocal類(lèi),它能夠讓子線程訪問(wèn)到在父線程中設(shè)置的本地變量的值,例如,我們將ThreadLocalTest類(lèi)中的threadLocal靜態(tài)變量改寫(xiě)成InheritableThreadLocal類(lèi)的實(shí)例,如下所示。
publicclassThreadLocalTest{ privatestaticThreadLocalthreadLocal=newInheritableThreadLocal (); publicstaticvoidmain(String[]args){ //在主線程中設(shè)置值 threadLocal.set("ThreadLocalTest"); //在子線程中獲取值 Threadthread=newThread(newRunnable(){ @Override publicvoidrun(){ System.out.println("子線程獲取值:"+threadLocal.get()); } }); //啟動(dòng)子線程 thread.start(); //在主線程中獲取值 System.out.println("主線程獲取值:"+threadLocal.get()); } }
此時(shí),運(yùn)行程序輸出的結(jié)果信息如下所示。
主線程獲取值:ThreadLocalTest 子線程獲取值:ThreadLocalTest
可以看到,使用InheritableThreadLocal類(lèi)存儲(chǔ)本地變量時(shí),子線程能夠獲取到父線程中設(shè)置的本地變量。
InheritableThreadLocal原理
首先,我們來(lái)看下InheritableThreadLocal類(lèi)的源碼,如下所示。
publicclassInheritableThreadLocalextendsThreadLocal { protectedTchildValue(TparentValue){ returnparentValue; } ThreadLocalMapgetMap(Threadt){ returnt.inheritableThreadLocals; } voidcreateMap(Threadt,TfirstValue){ t.inheritableThreadLocals=newThreadLocalMap(this,firstValue); } }
由InheritableThreadLocal類(lèi)的源代碼可知,InheritableThreadLocal類(lèi)繼承自ThreadLocal類(lèi),并且重寫(xiě)了ThreadLocal類(lèi)的childValue()方法、getMap()方法和createMap()方法。也就是說(shuō),當(dāng)調(diào)用ThreadLocal的set()方法時(shí),創(chuàng)建的是當(dāng)前Thread線程的inheritableThreadLocals成員變量而不再是threadLocals成員變量。
這里,我們需要思考一個(gè)問(wèn)題:InheritableThreadLocal類(lèi)的childValue()方法是何時(shí)被調(diào)用的呢?這就需要我們來(lái)看下Thread類(lèi)的構(gòu)造方法了,如下所示。
publicThread(){ init(null,null,"Thread-"+nextThreadNum(),0); } publicThread(Runnabletarget){ init(null,target,"Thread-"+nextThreadNum(),0); } Thread(Runnabletarget,AccessControlContextacc){ init(null,target,"Thread-"+nextThreadNum(),0,acc,false); } publicThread(ThreadGroupgroup,Runnabletarget){ init(group,target,"Thread-"+nextThreadNum(),0); } publicThread(Stringname){ init(null,null,name,0); } publicThread(ThreadGroupgroup,Stringname){ init(group,null,name,0); } publicThread(Runnabletarget,Stringname){ init(null,target,name,0); } publicThread(ThreadGroupgroup,Runnabletarget,Stringname){ init(group,target,name,0); } publicThread(ThreadGroupgroup,Runnabletarget,Stringname, longstackSize){ init(group,target,name,stackSize); }
可以看到,Thread類(lèi)的構(gòu)造方法最終調(diào)用的是init()方法,那我們就來(lái)看下init()方法,如下所示。
privatevoidinit(ThreadGroupg,Runnabletarget,Stringname, longstackSize,AccessControlContextacc, booleaninheritThreadLocals){ /************省略部分源碼************/ if(inheritThreadLocals&&parent.inheritableThreadLocals!=null) this.inheritableThreadLocals= ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); /*StashthespecifiedstacksizeincasetheVMcares*/ this.stackSize=stackSize; /*SetthreadID*/ tid=nextThreadID(); }
可以看到,在init()方法中會(huì)判斷傳遞的inheritThreadLocals變量是否為true,同時(shí)父線程中的inheritableThreadLocals是否為null,如果傳遞的inheritThreadLocals變量為true,同時(shí),父線程中的inheritableThreadLocals不為null,則調(diào)用ThreadLocal類(lèi)的createInheritedMap()方法。
staticThreadLocalMapcreateInheritedMap(ThreadLocalMapparentMap){ returnnewThreadLocalMap(parentMap); }
在createInheritedMap()中,使用父線程的inheritableThreadLocals變量作為參數(shù)創(chuàng)建新的ThreadLocalMap對(duì)象。然后在Thread類(lèi)的init()方法中會(huì)將這個(gè)ThreadLocalMap對(duì)象賦值給子線程的inheritableThreadLocals成員變量。
接下來(lái),我們來(lái)看看ThreadLocalMap的構(gòu)造函數(shù)都干了啥,如下所示。
privateThreadLocalMap(ThreadLocalMapparentMap){ Entry[]parentTable=parentMap.table; intlen=parentTable.length; setThreshold(len); table=newEntry[len]; for(intj=0;jkey=(ThreadLocal
在ThreadLocalMap的構(gòu)造函數(shù)中,調(diào)用了InheritableThreadLocal類(lèi)重寫(xiě)的childValue()方法。而InheritableThreadLocal類(lèi)通過(guò)重寫(xiě)getMap()方法和createMap()方法,讓本地變量保存到了Thread線程的inheritableThreadLocals變量中,線程通過(guò)InheritableThreadLocal類(lèi)的set()方法和get()方法設(shè)置變量時(shí),就會(huì)創(chuàng)建當(dāng)前線程的inheritableThreadLocals變量。
此時(shí),如果父線程創(chuàng)建子線程,在Thread類(lèi)的構(gòu)造函數(shù)中會(huì)把父線程中的inheritableThreadLocals變量里面的本地變量復(fù)制一份保存到子線程的inheritableThreadLocals變量中。
審核編輯:劉清
-
存儲(chǔ)器
+關(guān)注
關(guān)注
38文章
7640瀏覽量
166631 -
Thread編程
+關(guān)注
關(guān)注
0文章
3瀏覽量
942 -
內(nèi)存溢出
+關(guān)注
關(guān)注
0文章
10瀏覽量
1323
原文標(biāo)題:一文讓你徹底掌握ThreadLocal
文章出處:【微信號(hào):OSC開(kāi)源社區(qū),微信公眾號(hào):OSC開(kāi)源社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
一文徹底掌握MOS管
ThreadLocal實(shí)例應(yīng)用

ThreadLocal的定義、用法及優(yōu)點(diǎn)

讓你徹底理解DFT
ThreadLocal發(fā)生內(nèi)存泄漏的原因
如何使用ThreadLocal來(lái)避免內(nèi)存泄漏

評(píng)論