本篇內容介紹了“ThreadLocal的原理和用法”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
從名稱看,ThreadLocal 也就是thread和local的組合,也就是一個thread有一個local的變量副本 ThreadLocal提供了線程的本地副本,也就是說每個線程將會擁有一個自己獨立的變量副本
方法簡潔干練,類信息以及方法列表如下:
在測試類中定義了一個ThreadLocal變量,用于保存String類型數據 創建了兩個線程,分別設置值,讀取值,移除后再次讀取
package com.declan.threadlocal; /** * @author Declan * @date 2019/08/16 14:36 */ public class ThreadLocalDemo { public static ThreadLocal<String> threadLocal = new ThreadLocal<>(); public static void main(String[] args) { Thread thread1 = new Thread(()-> { //thread1中設置值 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中設置值 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"); //啟動兩個線程 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
從結果可以看得到,每個線程中可以有自己獨有的一份數據,互相沒有影響remove之后,數據被清空
從上面示例也可以看出來一個情況:
如果兩個線程同時對一個變量進行操作,互相之間是沒有影響的,換句話說,這很顯然并不是用來解決共享變量的一些并發問題,比如多線程的協作
因為ThreadLocal的設計理念就是共享變私有,都已經私有了,還談啥共享? 比如之前的消息隊列,生產者消費者的示例中final LinkedList<Message> messageQueue = new LinkedList<>();
如果這個LinkedList是ThreadLocal的,生產者使用一個,消費者使用一個,還協作什么呢?
但是共享變私有,如同并發變串行,或許適合解決一些場景的線程安全問題,因為看起來就如同沒有共享變量了,不共享即安全,但是他并不是為了解決線程安全問題而存在的
在Thread中有一個threadLocals變量,類型為ThreadLocal.ThreadLocalMap
而ThreadLocalMap則是ThreadLocal的靜態內部類,他是一個設計用來保存thread local 變量的自定義的hash map
也就是說在Thread中有一個“hashMap”可以用來保存鍵值對。
在這個方法中,接受參數,類型為T的value
首先獲取當前線程,然后調用getMap(t)
這個方法很簡單,就是直接返回Thread內部的那個“hashMap”(threadLocals是默認的訪問權限)
繼續回到set方法,如果這個map不為空,那么以this為key,value為值,也就是ThreadLocal變量作為key
如果map為空,那么進行給這個線程創建一個map ,并且將第一組值設置進去,key仍舊是這個ThreadLocal變量
調用一個ThreadLocal的set方法,會將:以這個ThreadLocal類型的變量為key,參數為value的這一個鍵值對,保存在Thread內部的一個“hashMap”中
在get方法內部仍舊是獲取當前線程的內部的這個“hashMap”,然后以當前對象this(ThreadLocal)作為key,進行值的獲取:
我們對這兩個方法換一個思路理解:
每個線程可能運行過程中,可能會操作很多的ThreadLocal變量,那么怎么區分各自?
直觀的理解就是,我們想要獲取某個線程的某個ThreadLocal變量的值
一個很好的解決方法就是借助于Map進行保存,ThreadLocal變量作為key,local值作為value
假設這個map名為:threadLocalsMap,可以提供setter和getter方法進行設置和讀取,內部為:
threadLocalsMap.set(ThreadLocal key,T value)
threadLocalsMap.get(ThreadLocal key)
這樣就可以達到thread --- local的效果,但是是否存在一些使用不便?我們內部定義的是ThreadLocal變量,但是只是用來作為key的?是否直接通過ThreadLocal進行值的獲取更加方便呢?
怎么能夠做到數據讀取的倒置?因為畢竟值的確是保存在Thread中的
其實也很簡單,只需要內部進行轉換就好了,對于下面兩個方法,我們都需要 ThreadLocal 作為key
threadLocalsMap.set(ThreadLocal key,T value)
threadLocalsMap.get(ThreadLocal key)
而這個key不就是這個ThreadLocal,不就是this 么
所以:
ThreadLocal.set(T value)就內部調用threadLocalsMap.set(this,T value)
ThreadLocal.get()就內部調用threadLocalsMap.get(this)
所以總結下就是:
每個Thread內部保存了一個"hashMap",key為ThreadLocal,這個線程操作了多少個ThreadLocal,就有多少個key
你想獲取一個ThreadLocal變量的值,就是ThreadLocal.get(),內部就是hashMap.get(this);
你想設置一個ThreadLocal變量的值,就是ThreadLocal.set(T value),內部就是hashMap.set(this,value);
關鍵只是在于內部的這個“hashMap”,ThreadLocal只是讀寫倒置的“殼”,可以更簡潔易用的通過這個殼進行變量的讀寫,“倒置”的紐帶,就是getMap(t)方法.
remove方法很簡單,當前線程,獲取當前線程的hashMap,remove
再次回頭看下get方法,如果第一次調用時,指定線程并沒有threadLocals,或者根本都沒有進行過set,會發生什么?
如下圖所示,會調用setInitialValue方法:
在setInitialValue方法中,會調用initialValue方法獲取初始值,如果該線程沒有threadLocals那么會創建,如果有,會使用這個初始值構造這個ThreadLocal的鍵值對,簡單說,如果沒有set過(或者壓根內部的這個threadLocals就是null的),那么她返回的值就是初始值
這個內部的initialValue方法默認的返回null,所以一個ThreadLocal如果沒有進行set操作,那么初始值為null:
如何進行初始值的設定?
可以看得出來,這是一個protected方法,所以返回一個覆蓋了這個方法的子類不就好了?在子類中實現初始值的設置。
通過set方法可以進行值的設定
通過get方法可以進行值的讀取,如果沒有進行過設置,那么將會返回null;如果使用了withInitial方法提供了初始值,將會返回初始值
通過remove方法將會移除對該值的寫入,再次調用get方法,如果使用了withInitial方法提供了初始值,將會返回初始值,否則返回null
對于get方法,很顯然如果沒有提供初始值,返回值為null,在使用時是需要注意不要引起NPE異常
ThreadLocal,thread local,每個線程一份,到底是什么意思?
他的意思是對于一個ThreadLocal類型變量,每個線程有一個對應的值,這個值的名字就是ThreadLocal類型變量的名字,值是我們set進去的變量, 但是如果set設置的是共享變量,那么ThreadLocal其實本質上還是同一個對象不是么?
這句話如果有疑問的話,可以這么理解:
對于同一個ThreadLocal變量a,每個線程有一個map,map中都有一個鍵值對,key為a,值為你保存的值,但是這個值,到底每個線程都是全新的?還是使用的同一個?這是你自己的問題了?。?!
ThreadLocal可以做到每個線程有一個獨立的一份值,但是你非得使用共享變量將他們設置成一個,那ThreadLocal是不會保障的。 這就好比一個對象,有很多引用指向他,每個線程有一個獨立的引用,但是對象根本還是只有一個。所以,從這個角度更容易理解,為什么說ThreadLocal并不是為了解決線程安全問題而設計的,因為他并不會為線程安全做什么保障,他的能力是持有多個引用,這多個引用是否能保障是多個不同的對象,你來決策!
所以我們最開始說的,ThreadLocal會為每個線程創建一個變量副本的說法是不嚴謹的, 是他有這個能力做到這件事情,但是到底是什么對象,還是要看你set的是什么,set本身不會對你的值進行干涉
前面說過,對于之前生產者消費者的示例中,就不適合使用ThreadLocal,因為問題模型就是要多線程之間協作,而不是為了線程安全就將共享變量私有化
比如,銀行賬戶的存款和取款,如果借助于ThreadLocal創建了兩個賬戶對象,就會有問題的,初始值500,明明又存進來1000塊,可支配的總額還是500
那ThreadLocal適合什么場景呢? 既然是每個線程一個,自然是適合那種希望每個線程擁有一個的那種場景(好像是廢話)
一個線程中一個,也就是線程隔離,既然是一個線程一個,那么同一個線程中調用的方法也就是共享了,所以說,有時,ThreadLocal會被用來作為參數傳遞的工具
因為它能夠保障同一個線程中的值是唯一的,那么他就共享于所有的方法中,對于所有的方法來說,相當于一個全局變量了!
所以可以用來同一個線程內全局參數傳遞
對于JavaWeb項目,大家都了解過Session
ps:此處不對session展開介紹,打開瀏覽器輸入網址,這就會建立一個session,關閉瀏覽器,session就失效了
在這個時間段內,一個用戶的多個請求中,共享同一個session,Session 保存了很多信息,有的需要通過 Session 獲取信息,有些又需要修改 Session 的信息。
每個線程需要獨立的session,而且很多地方都需要操作 Session,存在多方法共享 Session 的需求,所以session對象需要在多個方法中共享
如果不使用 ThreadLocal,可以在每個線程內創建一個 Session對象,然后在多個方法中將他作為參數進行傳遞
很顯然,如果每次都顯式的傳遞參數,繁瑣易錯
這種場景就適合使用ThreadLocal
下面的示例就模擬了多方法共享同一個session,但是線程間session隔離的示例:
package com.declan.threadlocal; /** * @author Declan * @date 2019/08/16 16:07 */ public class SessionDemo { /** * session變量定義 */ static ThreadLocal<Session> sessionThreadLocal = new ThreadLocal<Session>(); /** * 獲取session * @return */ public static Session getSession(){ if(sessionThreadLocal.get() == null){ sessionThreadLocal.set(new Session()); } return sessionThreadLocal.get(); } /** * 移除session */ public static void closeSession(){ sessionThreadLocal.remove(); } /** * 模擬一個調用session的方法1 */ public static void fun1(Session session){ } /** * 模擬一個調用session的方法2 */ public static void fun2(Session session){ } /** * 模擬session對象 */ static class Session{ } public static void main(String[] args) { Thread thread = new Thread(()->{ fun1(sessionThreadLocal.get()); fun2(sessionThreadLocal.get()); closeSession(); }); thread.start(); } }
所以,ThreadLocal最根本的使用場景應該是:
在每個線程希望有一個獨有的變量時(這個變量還很可能需要在同一個線程內共享),避免每個線程還需要主動地去創建這個對象(如果還需要共享,也一并解決了參數來回傳遞的問題), 換句話說就是,“如何優雅的解決:線程間隔離與線程內共享的問題”,而不是說用來解決亂七八糟的線程安全問題。
所以說如果有些場景你需要線程隔離,那么考慮ThreadLocal,而不是你有了什么線程安全問題需要解決,然后求助于ThreadLocal,這不是一回事
再次注意:
ThreadLocal只是具有這樣的能力,是你能夠做到每個線程一個獨有變量,但是如果你set時,不是傳遞的new出來的新變量,也就只是理解成“每個線程不同的引用”,對象還是那個對象(有點像參數傳遞時的值傳遞,對于對象傳遞的就是引用)
ThreadLocal可以用來優雅的解決線程間隔離的對象,必須主動創建的問題,借助于ThreadLocal無需在線程中顯式的創建對象,解決方案很優雅
ThreadLocal中的set方法并不會保障的確是每個線程會獲得不同的對象,你需要對邏輯進行一定的處理(比如上面的示例中的getSession方法,如果ThreadLocal 變量的get為null,那么new對象) 是否真的能夠做到線程隔離,還要看你自己的編碼實現,不過如果是共享變量,你還放到ThreadLocal中干嘛?
所以通常都是線程獨有的對象,通過new創建。
“ThreadLocal的原理和用法”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。