本篇內(nèi)容介紹了“ThreadLocal的原理和用法”的有關(guān)知識(shí),在實(shí)際案例的操作過(guò)程中,不少人都會(huì)遇到這樣的困境,接下來(lái)就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
創(chuàng)新互聯(lián)專(zhuān)注于中小企業(yè)網(wǎng)站建設(shè)、策劃制作、運(yùn)行維護(hù),主要提供一站式的企業(yè)網(wǎng)站建設(shè)服務(wù)。建站類(lèi)型:公司網(wǎng)站建設(shè)、品牌網(wǎng)站建設(shè)、成都外貿(mào)網(wǎng)站制作獨(dú)立站等。創(chuàng)新互聯(lián)不是單一的建網(wǎng)站,而是結(jié)合企業(yè)的建站目標(biāo)去規(guī)劃網(wǎng)站怎么建,如何利于運(yùn)營(yíng),尋求適合的建站方案。其次,網(wǎng)站后臺(tái)操作的便捷性也是網(wǎng)站制作過(guò)程中的重點(diǎn),創(chuàng)新互聯(lián)的網(wǎng)站后臺(tái)簡(jiǎn)單便捷,真正實(shí)現(xiàn)了零基礎(chǔ)操作。
從名稱(chēng)看,ThreadLocal 也就是thread和local的組合,也就是一個(gè)thread有一個(gè)local的變量副本 ThreadLocal提供了線程的本地副本,也就是說(shuō)每個(gè)線程將會(huì)擁有一個(gè)自己獨(dú)立的變量副本
方法簡(jiǎn)潔干練,類(lèi)信息以及方法列表如下:
在測(cè)試類(lèi)中定義了一個(gè)ThreadLocal變量,用于保存String類(lèi)型數(shù)據(jù) 創(chuàng)建了兩個(gè)線程,分別設(shè)置值,讀取值,移除后再次讀取
package com.declan.threadlocal; /** * @author Declan * @date 2019/08/16 14:36 */ public class ThreadLocalDemo { public static ThreadLocalthreadLocal = new ThreadLocal<>(); public static void main(String[] args) { Thread thread1 = new Thread(()-> { //thread1中設(shè)置值 threadLocal.set("this is thread1's local"); //獲取值 System.out.println(Thread.currentThread().getName()+": threadLocal value:" + threadLocal.get()); //移除值 threadLocal.remove(); //再次獲取 System.out.println(Thread.currentThread().getName()+": after remove threadLocal value:" + threadLocal.get()); }, "thread1"); Thread thread2 = new Thread(() -> { //thread1中設(shè)置值 threadLocal.set("this is thread2's local"); //獲取值 System.out.println(Thread.currentThread().getName()+": threadLocal value:" + threadLocal.get()); //移除值 threadLocal.remove(); //再次獲取 System.out.println(Thread.currentThread().getName()+": after remove threadLocal value:" + threadLocal.get()); }, "thread2"); //啟動(dòng)兩個(gè)線程 thread1.start(); thread2.start(); } }
thread2: threadLocal value:this is thread2's local thread2: after remove threadLocal value:null thread1: threadLocal value:this is thread1's local thread1: after remove threadLocal value:null
從結(jié)果可以看得到,每個(gè)線程中可以有自己獨(dú)有的一份數(shù)據(jù),互相沒(méi)有影響remove之后,數(shù)據(jù)被清空
從上面示例也可以看出來(lái)一個(gè)情況:
如果兩個(gè)線程同時(shí)對(duì)一個(gè)變量進(jìn)行操作,互相之間是沒(méi)有影響的,換句話說(shuō),這很顯然并不是用來(lái)解決共享變量的一些并發(fā)問(wèn)題,比如多線程的協(xié)作
因?yàn)門(mén)hreadLocal的設(shè)計(jì)理念就是共享變私有,都已經(jīng)私有了,還談啥共享? 比如之前的消息隊(duì)列,生產(chǎn)者消費(fèi)者的示例中final LinkedList
如果這個(gè)LinkedList是ThreadLocal的,生產(chǎn)者使用一個(gè),消費(fèi)者使用一個(gè),還協(xié)作什么呢?
但是共享變私有,如同并發(fā)變串行,或許適合解決一些場(chǎng)景的線程安全問(wèn)題,因?yàn)榭雌饋?lái)就如同沒(méi)有共享變量了,不共享即安全,但是他并不是為了解決線程安全問(wèn)題而存在的
在Thread中有一個(gè)threadLocals變量,類(lèi)型為ThreadLocal.ThreadLocalMap
而ThreadLocalMap則是ThreadLocal的靜態(tài)內(nèi)部類(lèi),他是一個(gè)設(shè)計(jì)用來(lái)保存thread local 變量的自定義的hash map
也就是說(shuō)在Thread中有一個(gè)“hashMap”可以用來(lái)保存鍵值對(duì)。
在這個(gè)方法中,接受參數(shù),類(lèi)型為T(mén)的value
首先獲取當(dāng)前線程,然后調(diào)用getMap(t)
這個(gè)方法很簡(jiǎn)單,就是直接返回Thread內(nèi)部的那個(gè)“hashMap”(threadLocals是默認(rèn)的訪問(wèn)權(quán)限)
繼續(xù)回到set方法,如果這個(gè)map不為空,那么以this為key,value為值,也就是ThreadLocal變量作為key
如果map為空,那么進(jìn)行給這個(gè)線程創(chuàng)建一個(gè)map ,并且將第一組值設(shè)置進(jìn)去,key仍舊是這個(gè)ThreadLocal變量
調(diào)用一個(gè)ThreadLocal的set方法,會(huì)將:以這個(gè)ThreadLocal類(lèi)型的變量為key,參數(shù)為value的這一個(gè)鍵值對(duì),保存在Thread內(nèi)部的一個(gè)“hashMap”中
在get方法內(nèi)部仍舊是獲取當(dāng)前線程的內(nèi)部的這個(gè)“hashMap”,然后以當(dāng)前對(duì)象this(ThreadLocal)作為key,進(jìn)行值的獲取:
我們對(duì)這兩個(gè)方法換一個(gè)思路理解:
每個(gè)線程可能運(yùn)行過(guò)程中,可能會(huì)操作很多的ThreadLocal變量,那么怎么區(qū)分各自?
直觀的理解就是,我們想要獲取某個(gè)線程的某個(gè)ThreadLocal變量的值
一個(gè)很好的解決方法就是借助于Map進(jìn)行保存,ThreadLocal變量作為key,local值作為value
假設(shè)這個(gè)map名為:threadLocalsMap,可以提供setter和getter方法進(jìn)行設(shè)置和讀取,內(nèi)部為:
threadLocalsMap.set(ThreadLocal key,T value)
threadLocalsMap.get(ThreadLocal key)
這樣就可以達(dá)到thread --- local的效果,但是是否存在一些使用不便?我們內(nèi)部定義的是ThreadLocal變量,但是只是用來(lái)作為key的?是否直接通過(guò)ThreadLocal進(jìn)行值的獲取更加方便呢?
怎么能夠做到數(shù)據(jù)讀取的倒置?因?yàn)楫吘怪档拇_是保存在Thread中的
其實(shí)也很簡(jiǎn)單,只需要內(nèi)部進(jìn)行轉(zhuǎn)換就好了,對(duì)于下面兩個(gè)方法,我們都需要 ThreadLocal 作為key
threadLocalsMap.set(ThreadLocal key,T value)
threadLocalsMap.get(ThreadLocal key)
而這個(gè)key不就是這個(gè)ThreadLocal,不就是this 么
所以:
ThreadLocal.set(T value)就內(nèi)部調(diào)用threadLocalsMap.set(this,T value)
ThreadLocal.get()就內(nèi)部調(diào)用threadLocalsMap.get(this)
所以總結(jié)下就是:
每個(gè)Thread內(nèi)部保存了一個(gè)"hashMap",key為T(mén)hreadLocal,這個(gè)線程操作了多少個(gè)ThreadLocal,就有多少個(gè)key
你想獲取一個(gè)ThreadLocal變量的值,就是ThreadLocal.get(),內(nèi)部就是hashMap.get(this);
你想設(shè)置一個(gè)ThreadLocal變量的值,就是ThreadLocal.set(T value),內(nèi)部就是hashMap.set(this,value);
關(guān)鍵只是在于內(nèi)部的這個(gè)“hashMap”,ThreadLocal只是讀寫(xiě)倒置的“殼”,可以更簡(jiǎn)潔易用的通過(guò)這個(gè)殼進(jìn)行變量的讀寫(xiě),“倒置”的紐帶,就是getMap(t)方法.
remove方法很簡(jiǎn)單,當(dāng)前線程,獲取當(dāng)前線程的hashMap,remove
再次回頭看下get方法,如果第一次調(diào)用時(shí),指定線程并沒(méi)有threadLocals,或者根本都沒(méi)有進(jìn)行過(guò)set,會(huì)發(fā)生什么?
如下圖所示,會(huì)調(diào)用setInitialValue方法:
在setInitialValue方法中,會(huì)調(diào)用initialValue方法獲取初始值,如果該線程沒(méi)有threadLocals那么會(huì)創(chuàng)建,如果有,會(huì)使用這個(gè)初始值構(gòu)造這個(gè)ThreadLocal的鍵值對(duì),簡(jiǎn)單說(shuō),如果沒(méi)有set過(guò)(或者壓根內(nèi)部的這個(gè)threadLocals就是null的),那么她返回的值就是初始值
這個(gè)內(nèi)部的initialValue方法默認(rèn)的返回null,所以一個(gè)ThreadLocal如果沒(méi)有進(jìn)行set操作,那么初始值為null:
如何進(jìn)行初始值的設(shè)定?
可以看得出來(lái),這是一個(gè)protected方法,所以返回一個(gè)覆蓋了這個(gè)方法的子類(lèi)不就好了?在子類(lèi)中實(shí)現(xiàn)初始值的設(shè)置。
通過(guò)set方法可以進(jìn)行值的設(shè)定
通過(guò)get方法可以進(jìn)行值的讀取,如果沒(méi)有進(jìn)行過(guò)設(shè)置,那么將會(huì)返回null;如果使用了withInitial方法提供了初始值,將會(huì)返回初始值
通過(guò)remove方法將會(huì)移除對(duì)該值的寫(xiě)入,再次調(diào)用get方法,如果使用了withInitial方法提供了初始值,將會(huì)返回初始值,否則返回null
對(duì)于get方法,很顯然如果沒(méi)有提供初始值,返回值為null,在使用時(shí)是需要注意不要引起NPE異常
ThreadLocal,thread local,每個(gè)線程一份,到底是什么意思?
他的意思是對(duì)于一個(gè)ThreadLocal類(lèi)型變量,每個(gè)線程有一個(gè)對(duì)應(yīng)的值,這個(gè)值的名字就是ThreadLocal類(lèi)型變量的名字,值是我們set進(jìn)去的變量, 但是如果set設(shè)置的是共享變量,那么ThreadLocal其實(shí)本質(zhì)上還是同一個(gè)對(duì)象不是么?
這句話如果有疑問(wèn)的話,可以這么理解:
對(duì)于同一個(gè)ThreadLocal變量a,每個(gè)線程有一個(gè)map,map中都有一個(gè)鍵值對(duì),key為a,值為你保存的值,但是這個(gè)值,到底每個(gè)線程都是全新的?還是使用的同一個(gè)?這是你自己的問(wèn)題了?。?!
ThreadLocal可以做到每個(gè)線程有一個(gè)獨(dú)立的一份值,但是你非得使用共享變量將他們?cè)O(shè)置成一個(gè),那ThreadLocal是不會(huì)保障的。 這就好比一個(gè)對(duì)象,有很多引用指向他,每個(gè)線程有一個(gè)獨(dú)立的引用,但是對(duì)象根本還是只有一個(gè)。所以,從這個(gè)角度更容易理解,為什么說(shuō)ThreadLocal并不是為了解決線程安全問(wèn)題而設(shè)計(jì)的,因?yàn)樗⒉粫?huì)為線程安全做什么保障,他的能力是持有多個(gè)引用,這多個(gè)引用是否能保障是多個(gè)不同的對(duì)象,你來(lái)決策!
所以我們最開(kāi)始說(shuō)的,ThreadLocal會(huì)為每個(gè)線程創(chuàng)建一個(gè)變量副本的說(shuō)法是不嚴(yán)謹(jǐn)?shù)模?是他有這個(gè)能力做到這件事情,但是到底是什么對(duì)象,還是要看你set的是什么,set本身不會(huì)對(duì)你的值進(jìn)行干涉
前面說(shuō)過(guò),對(duì)于之前生產(chǎn)者消費(fèi)者的示例中,就不適合使用ThreadLocal,因?yàn)閱?wèn)題模型就是要多線程之間協(xié)作,而不是為了線程安全就將共享變量私有化
比如,銀行賬戶的存款和取款,如果借助于ThreadLocal創(chuàng)建了兩個(gè)賬戶對(duì)象,就會(huì)有問(wèn)題的,初始值500,明明又存進(jìn)來(lái)1000塊,可支配的總額還是500
那ThreadLocal適合什么場(chǎng)景呢? 既然是每個(gè)線程一個(gè),自然是適合那種希望每個(gè)線程擁有一個(gè)的那種場(chǎng)景(好像是廢話)
一個(gè)線程中一個(gè),也就是線程隔離,既然是一個(gè)線程一個(gè),那么同一個(gè)線程中調(diào)用的方法也就是共享了,所以說(shuō),有時(shí),ThreadLocal會(huì)被用來(lái)作為參數(shù)傳遞的工具
因?yàn)樗軌虮U贤粋€(gè)線程中的值是唯一的,那么他就共享于所有的方法中,對(duì)于所有的方法來(lái)說(shuō),相當(dāng)于一個(gè)全局變量了!
所以可以用來(lái)同一個(gè)線程內(nèi)全局參數(shù)傳遞
對(duì)于JavaWeb項(xiàng)目,大家都了解過(guò)Session
ps:此處不對(duì)session展開(kāi)介紹,打開(kāi)瀏覽器輸入網(wǎng)址,這就會(huì)建立一個(gè)session,關(guān)閉瀏覽器,session就失效了
在這個(gè)時(shí)間段內(nèi),一個(gè)用戶的多個(gè)請(qǐng)求中,共享同一個(gè)session,Session 保存了很多信息,有的需要通過(guò) Session 獲取信息,有些又需要修改 Session 的信息。
每個(gè)線程需要獨(dú)立的session,而且很多地方都需要操作 Session,存在多方法共享 Session 的需求,所以session對(duì)象需要在多個(gè)方法中共享
如果不使用 ThreadLocal,可以在每個(gè)線程內(nèi)創(chuàng)建一個(gè) Session對(duì)象,然后在多個(gè)方法中將他作為參數(shù)進(jìn)行傳遞
很顯然,如果每次都顯式的傳遞參數(shù),繁瑣易錯(cuò)
這種場(chǎng)景就適合使用ThreadLocal
下面的示例就模擬了多方法共享同一個(gè)session,但是線程間session隔離的示例:
package com.declan.threadlocal; /** * @author Declan * @date 2019/08/16 16:07 */ public class SessionDemo { /** * session變量定義 */ static ThreadLocalsessionThreadLocal = new ThreadLocal (); /** * 獲取session * @return */ public static Session getSession(){ if(sessionThreadLocal.get() == null){ sessionThreadLocal.set(new Session()); } return sessionThreadLocal.get(); } /** * 移除session */ public static void closeSession(){ sessionThreadLocal.remove(); } /** * 模擬一個(gè)調(diào)用session的方法1 */ public static void fun1(Session session){ } /** * 模擬一個(gè)調(diào)用session的方法2 */ public static void fun2(Session session){ } /** * 模擬session對(duì)象 */ static class Session{ } public static void main(String[] args) { Thread thread = new Thread(()->{ fun1(sessionThreadLocal.get()); fun2(sessionThreadLocal.get()); closeSession(); }); thread.start(); } }
所以,ThreadLocal最根本的使用場(chǎng)景應(yīng)該是:
在每個(gè)線程希望有一個(gè)獨(dú)有的變量時(shí)(這個(gè)變量還很可能需要在同一個(gè)線程內(nèi)共享),避免每個(gè)線程還需要主動(dòng)地去創(chuàng)建這個(gè)對(duì)象(如果還需要共享,也一并解決了參數(shù)來(lái)回傳遞的問(wèn)題), 換句話說(shuō)就是,“如何優(yōu)雅的解決:線程間隔離與線程內(nèi)共享的問(wèn)題”,而不是說(shuō)用來(lái)解決亂七八糟的線程安全問(wèn)題。
所以說(shuō)如果有些場(chǎng)景你需要線程隔離,那么考慮ThreadLocal,而不是你有了什么線程安全問(wèn)題需要解決,然后求助于ThreadLocal,這不是一回事
再次注意:
ThreadLocal只是具有這樣的能力,是你能夠做到每個(gè)線程一個(gè)獨(dú)有變量,但是如果你set時(shí),不是傳遞的new出來(lái)的新變量,也就只是理解成“每個(gè)線程不同的引用”,對(duì)象還是那個(gè)對(duì)象(有點(diǎn)像參數(shù)傳遞時(shí)的值傳遞,對(duì)于對(duì)象傳遞的就是引用)
ThreadLocal可以用來(lái)優(yōu)雅的解決線程間隔離的對(duì)象,必須主動(dòng)創(chuàng)建的問(wèn)題,借助于ThreadLocal無(wú)需在線程中顯式的創(chuàng)建對(duì)象,解決方案很優(yōu)雅
ThreadLocal中的set方法并不會(huì)保障的確是每個(gè)線程會(huì)獲得不同的對(duì)象,你需要對(duì)邏輯進(jìn)行一定的處理(比如上面的示例中的getSession方法,如果ThreadLocal 變量的get為null,那么new對(duì)象) 是否真的能夠做到線程隔離,還要看你自己的編碼實(shí)現(xiàn),不過(guò)如果是共享變量,你還放到ThreadLocal中干嘛?
所以通常都是線程獨(dú)有的對(duì)象,通過(guò)new創(chuàng)建。
“ThreadLocal的原理和用法”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!