Spring中ThreadLocal的解析
我們知道Spring
通過各種DAO模板類降低了開發(fā)者使用各種數(shù)據(jù)持久技術(shù)的難度。這些模板類都是線程安全的,也就是說,多個(gè)DAO可以復(fù)用同一個(gè)模板實(shí)例而不會(huì)發(fā)生沖突。 我們使用模板類訪問底層數(shù)據(jù),根據(jù)持久化技術(shù)的不同,模板類需要綁定數(shù)據(jù)連接或會(huì)話的資源。但這些資源本身是非線程安全的,也就是說它們不能在同一時(shí)刻被多個(gè)線程共享。
雖然模板類通過資源池獲取數(shù)據(jù)連接或會(huì)話,但資源池本身解決的是數(shù)據(jù)連接或會(huì)話的緩存問題,并非數(shù)據(jù)連接或會(huì)話的線程安全問題。
按照傳統(tǒng)經(jīng)驗(yàn),如果某個(gè)對(duì)象是非線程安全的,在多線程環(huán)境下,對(duì)對(duì)象的訪問必須采用synchronized
進(jìn)行線程同步。但Spring的DAO模板類并未采用線程同步機(jī)制,因?yàn)榫€程同步限制了并發(fā)訪問,會(huì)帶來(lái)很大的性能損失。此外,通過代碼同步解決性能安全問題挑戰(zhàn)性很大,可能會(huì)增強(qiáng)好幾倍的實(shí)現(xiàn)難度。那模板類究竟仰丈何種魔法神功,可以在無(wú)需同步的情況下就化解線程安全的難題呢?答案就是ThreadLocal
!
ThreadLocal
在Spring中發(fā)揮著重要的作用,在管理request作用域的Bean、事務(wù)管理、任務(wù)調(diào)度、AOP等模塊都出現(xiàn)了它們的身影,起著舉足輕重的作用。要想了解Spring事務(wù)管理的底層技術(shù),ThreadLocal是必須攻克的山頭堡壘。
ThreadLocal是什么
早在JDK1.2的版本中就提供java.lang.ThreadLocal
,ThreadLocal為解決多線程程序的并發(fā)問題提供了一種新的思路。使用這個(gè)工具類可以很簡(jiǎn)潔地編寫出優(yōu)美的多線程程序。
ThreadLocal很容易讓人望文生義,想當(dāng)然地認(rèn)為是一個(gè)“本地線程”。其實(shí),ThreadLocal并不是一個(gè)Thread,而是Thread的局部變量,也許把它命名為ThreadLocalVariable
更容易讓人理解一些。
當(dāng)使用ThreadLocal維護(hù)變量時(shí),ThreadLocal
為每個(gè)使用該變量的線程提供獨(dú)立的變量副本,所以每一個(gè)線程都可以獨(dú)立地改變自己的副本,而不會(huì)影響其它線程所對(duì)應(yīng)的副本。
從線程的角度看,目標(biāo)變量就象是線程的本地變量,這也是類名中“Local”所要表達(dá)的意思。
線程局部變量并不是Java的新發(fā)明,很多語(yǔ)言(如IBM IBM XLFORTRAN)在語(yǔ)法層面就提供線程局部變量。在Java中沒有提供在語(yǔ)言級(jí)支持,而是變相地通過ThreadLocal
的類提供支持。
所以,在Java中編寫線程局部變量的代碼相對(duì)來(lái)說要笨拙一些,因此造成線程局部變量沒有在Java開發(fā)者中得到很好的普及。
1.ThreadLocal的接口方法
ThreadLocal類接口很簡(jiǎn)單,只有4個(gè)方法,我們先來(lái)了解一下:
void set(Object value)
置當(dāng)前線程的線程局部變量的值:
ublic Object get()
該方法返回當(dāng)前線程所對(duì)應(yīng)的線程局部變量:
public void remove()
將當(dāng)前線程局部變量的值刪除,目的是為了減少內(nèi)存的占用,該方法是JDK5.0新增的方法。需要指出的是,當(dāng)線程結(jié)束后,對(duì)應(yīng)該線程的局部變量將自動(dòng)被垃圾回收,所以顯式調(diào)用該方法清除線程的局部變量并不是必須的操作,但它可以加快內(nèi)存回收的速度。
protected Object initialValue()
返回該線程局部變量的初始值,該方法是一個(gè)protected的方法,顯然是為了讓子類覆蓋而設(shè)計(jì)的。這個(gè)方法是一個(gè)延遲調(diào)用方法,在線程第1次調(diào)用get()
或set(Object)時(shí)才執(zhí)行,并且僅執(zhí)行1次。ThreadLocal
中的缺省實(shí)現(xiàn)直接返回一個(gè)null。
值得一提的是,在JDK5.0中,ThreadLocal已經(jīng)支持泛型,該類的類名已經(jīng)變?yōu)門hreadLocal<T>。API方法也相應(yīng)進(jìn)行了調(diào)整,新版本的API方法分別是voidset(T value)、T get()以及T initialValue()。
ThreadLocal是如何做到為每一個(gè)線程維護(hù)變量的副本的呢?其實(shí)實(shí)現(xiàn)的思路很簡(jiǎn)單:在ThreadLocal類中有一個(gè)Map,用于存儲(chǔ)每一個(gè)線程的變量副本,Map中元素的鍵為線程對(duì)象,而值對(duì)應(yīng)線程的變量副本。
我們自己就可以提供一個(gè)簡(jiǎn)單的實(shí)現(xiàn)版本:
? public class SimpleThreadLocal { ? ? ? ? ? ? ? ? ? ?? ? ? ? ? ? ? ? ? private Map valueMap = Collections.synchronizedMap(new HashMap()); ? ? ? ?? ? ? ? ? ? ? ? ? public void set(Object newValue) { ? ? ? ? ? ? ? ? ? ? ? valueMap.put(Thread.currentThread(), newValue); //①鍵為線程對(duì)象,值為本線程的變量副本 ? ? ? ? ? ? ? ? ? } ? ? ? ?? ? ? ? ? ? ? ? ? public Object get() { ? ? ? ? ? ? ? ? ? ? ? Thread currentThread = Thread.currentThread(); ? ? ? ? ? ? ? ? ? ? ? Object o = valueMap.get(currentThread); ? ? //②返回本線程對(duì)應(yīng)的變量 ? ? ? ? ? ?? ? ? ? ? ? ? ? ? ? ? if (o == null && !valueMap.containsKey(currentThread)) {//③如果在Map中不存在,放到Map中保存起來(lái)。 ? ? ? ? ? ? ? ? ? ? ? ? ? o = initialValue(); ? ? ? ? ? ? ? ? ? ? ? ? ? valueMap.put(currentThread, o); ? ? ? ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? ? ? ? ?? ? ? ? ? ? ? ? ? ? ? return o; ? ? ? ? ? ? ? ? ? } ? ? ? ?? ? ? ? ? ? ? ? ? public void remove() { ? ? ? ? ? ? ? ? ? ? ? valueMap.remove(Thread.currentThread()); ? ? ? ? ? ? ? ? ? } ? ? ? ?? ? ? ? ? ? ? ? ? public Object initialValue() { ? ? ? ? ? ? ? ? ? ? ? return null; ? ? ? ? ? ? ? ? ? } ? ? ? ?? ? ? ? ?} ?
雖然上述代碼清單這個(gè)ThreadLocal
實(shí)現(xiàn)版本顯得比較幼稚,但它和JDK所提供的ThreadLocal
類在實(shí)現(xiàn)思路上是相近的。
2.TheadLocal實(shí)例
下面,我們通過一個(gè)具體的實(shí)例了解一下ThreadLocal
的具體使用方法
package threadLocalDemo; ?? ? ?? ??? ?public class SequenceNumber{ ? ?? ??? ??? ? ?//①通過匿名內(nèi)部類覆蓋ThreadLocal的initialValue()方法,指定初始值--初始化 ?? ??? ??? ?//ThreadLocal能夠保證每個(gè)線程在使用該變量的時(shí)候都擁有一份獨(dú)立的變量。Map實(shí)現(xiàn) ?? ??? ??? ?private static ThreadLocal<Integer> seqNum =new ThreadLocal<Integer>(){ ?? ??? ??? ? ? ? ? public Integer initialValue() { ?? ??? ??? ? ? ? ? ? ? return 0; //初始化時(shí)候?yàn)? ?? ??? ??? ? ? ? ? }?? ??? ? ?? ??? ??? ? ?}; ?? ??? ? ?? ??? ? ? ?//②獲取下一個(gè)序列值?? ? ?? ??? ? ? ?public int getNextNum(){?? ? ?? ??? ? ? ? ? seqNum.set(seqNum.get()+ 1); ?? ??? ? ? ? ? return seqNum.get(); ?? ??? ? ? ?} ?? ??? ? ?? ??? ? ? ?public static void main(String[] args){ ?? ? ?? ??? ? ? ? ? SequenceNumber sn = new SequenceNumber(); ?? ? ?? ??? ? ? ? ? // ③ 3個(gè)線程共享sn,各自產(chǎn)生序列號(hào)?? ? ?? ??? ? ? ? ? TestClient t1 = new TestClient(sn); ?? ??? ? ? ? ? TestClient t2 = new TestClient(sn);?? ? ?? ??? ? ? ? ? TestClient t3 = new TestClient(sn); ?? ? ?? ??? ? ? ? ? t1.start();?? ? ?? ??? ? ? ? ? t2.start();?? ? ?? ??? ? ? ? ? t3.start();?? ? ?? ??? ? ? ?} ?? ??? ? ?? ??? ??? ? ? ?private static class TestClient extends Thread{?? ? ?? ??? ??? ? ? ? ? private SequenceNumber sn; ?? ??? ? ?? ??? ??? ? ? ? ? public TestClient(SequenceNumber sn) {?? ? ?? ??? ??? ? ? ? ? ? ? this.sn= sn; ?? ??? ??? ? ? ? ? } ?? ??? ? ?? ??? ??? ? ? ? ? public void run() {?? ? ?? ??? ??? ? ? ? ? ? ? for (int i= 0; i < 3;i++) {?? ? ?? ??? ??? ? ? ? ? ? ? ? ? //④每個(gè)線程打出3個(gè)序列值?? ? ?? ??? ??? ? ? ? ? ? ? ? ? System.out.println("thread[" + Thread.currentThread().getName()+"]sn[" +sn.getNextNum() + "]");?? ? ?? ??? ??? ? ? ? ? ? ? }?? ? ?? ??? ??? ? ? ? ? } ?? ??? ? ?? ??? ??? ? ? ?} ?? ? ?? ??? ?}
通常我們通過匿名內(nèi)部類的方式定義ThreadLocal
的子類,提供初始的變量值,如例子中①處所示。TestClient線程產(chǎn)生一組序列號(hào),在③處,我們生成3個(gè)TestClient,它們共享同一個(gè)SequenceNumber
實(shí)例。
運(yùn)行以上代碼,在控制臺(tái)上輸出以下的結(jié)果:
thread[Thread-2] sn[1]
thread[Thread-0] sn[1]
thread[Thread-1] sn[1]
thread[Thread-2] sn[2]
thread[Thread-0] sn[2]
thread[Thread-1] sn[2]
thread[Thread-2] sn[3]
thread[Thread-0] sn[3]
thread[Thread-1] sn[3]
輸出的結(jié)果信息,我們發(fā)現(xiàn)每個(gè)線程所產(chǎn)生的序號(hào)雖然都共享同一個(gè)SequenceNumber
實(shí)例,但它們并沒有發(fā)生相互干擾的情況,而是各自產(chǎn)生獨(dú)立的序列號(hào),這是因?yàn)槲覀兺ㄟ^ThreadLocal
為每一個(gè)線程提供了單獨(dú)的副本。
3.Thread同步機(jī)制的比較(總結(jié))
ThreadLocal
和線程同步機(jī)制相比有什么優(yōu)勢(shì)呢?ThreadLocal
和線程同步機(jī)制都是為了解決多線程中相同變量的訪問沖突問題。
在同步機(jī)制中,通過對(duì)象的鎖機(jī)制保證同一時(shí)間只有一個(gè)線程訪問變量。這時(shí)該變量是多個(gè)線程共享的,使用同步機(jī)制要求程序慎密地分析什么時(shí)候?qū)ψ兞窟M(jìn)行讀寫,什么時(shí)候需要鎖定某個(gè)對(duì)象,什么時(shí)候釋放對(duì)象鎖等繁雜的問題,程序設(shè)計(jì)和編寫難度相對(duì)較大。
而ThreadLocal則從另一個(gè)角度來(lái)解決多線程的并發(fā)訪問。ThreadLocal會(huì)為每一個(gè)線程提供一個(gè)獨(dú)立的變量副本,從而隔離了多個(gè)線程對(duì)數(shù)據(jù)的訪問沖突。因?yàn)槊恳粋€(gè)線程都擁有自己的變量副本,從而也就沒有必要對(duì)該變量進(jìn)行同步了。ThreadLocal提供了線程安全的共享對(duì)象,在編寫多線程代碼時(shí),可以把不安全的變量封裝進(jìn)ThreadLocal。
由于ThreadLocal中可以持有任何類型的對(duì)象,低版本JDK所提供的get()返回的是Object對(duì)象,需要強(qiáng)制類型轉(zhuǎn)換。但JDK5.0通過泛型很好的解決了這個(gè)問題,在一定程度地簡(jiǎn)化ThreadLocal
的使用,代碼清單 9 2就使用了JDK5.0新的ThreadLocal<T>版本。
概括起來(lái)說,對(duì)于多線程資源共享的問題,同步機(jī)制采用了“以時(shí)間換空間”的方式,而ThreadLocal
采用了“以空間換時(shí)間”的方式。前者僅提供一份變量,讓不同的線程排隊(duì)訪問,而后者為每一個(gè)線程都提供了一份變量,因此可以同時(shí)訪問而互不影響。
4.Spring使用ThreadLocal解決線程安全問題
spring中對(duì)于并發(fā)情況的處理(非常好)
我們知道在一般情況下,只有無(wú)狀態(tài)的Bean才可以在多線程環(huán)境下共享,參考Spring Bean Scope 有狀態(tài)的Bean 與無(wú)狀態(tài)的Bean ,在Spring中,絕大部分Bean都可以聲明為singleton作用域,因?yàn)槭菬o(wú)狀態(tài)的嘛。對(duì)于有狀態(tài)的Bean,就是因?yàn)镾pring對(duì)一些Bean(如RequestContextHolder
、TransactionSynchronizationManager
、LocaleContextHolder
等)中非線程安全狀態(tài)采用ThreadLocal進(jìn)行處理,讓它們也成為線程安全的狀態(tài),因此有狀態(tài)的Bean就可以在多線程中共享了。
一般的Web應(yīng)用劃分為展現(xiàn)層、服務(wù)層和持久層三個(gè)層次,在不同的層中編寫對(duì)應(yīng)的邏輯,下層通過接口向上層開放功能調(diào)用。在一般情況下,從接收請(qǐng)求到返回響應(yīng)所經(jīng)過的所有程序調(diào)用都同屬于一個(gè)線程,
樣你就可以根據(jù)需要,將一些非線程安全的變量以ThreadLocal存放,在同一次請(qǐng)求響應(yīng)的調(diào)用線程中,所有關(guān)聯(lián)的對(duì)象引用到的都是同一個(gè)變量。
下面的實(shí)例能夠體現(xiàn)Spring對(duì)有狀態(tài)Bean的改造思路:
代碼清單3 TopicDao:非線程安全
public class TopicDao{ ?? ? ?? ??? ?private Connection conn;//①一個(gè)非線程安全的變量 ?? ? ?? ??? ?public void addTopic(){ ?? ??? ??? ?Statement stat = conn.createStatement();//②引用非線程安全變量 ?? ??? ? ?? ??? ??? ?… ?? ??? ?} ?? ? ?? ?}
由于①處的conn是成員變量,因?yàn)閍ddTopic()方法是非線程安全的,必須在使用時(shí)創(chuàng)建一個(gè)新TopicDao實(shí)例(非singleton)。
下面使用ThreadLocal對(duì)conn這個(gè)非線程安全的“狀態(tài)”進(jìn)行改造:
public class SqlConnection { ?? ? ?? ??? ? ? ?//①使用ThreadLocal保存Connection變量 ?? ??? ??? ?private static ThreadLocal<Connection> connThreadLocal = new ThreadLocal<Connection>(); ?? ? ?? ??? ? ? ?public static Connection getConnection() { ? ?? ??? ? ? ? ? //②如果connThreadLocal沒有本線程對(duì)應(yīng)的Connection,創(chuàng)建一個(gè)新的Connection,并將其保存到線程本地變量中。 ?? ??? ? ? ??? ?//如果線程變量中存在對(duì)應(yīng)的Connection,那么就取出來(lái) ?? ??? ? ? ? ? if (connThreadLocal.get()== null){ ?? ??? ? ? ? ? ? ? Connection conn = getConnection(); ?? ??? ? ? ? ? ? ? connThreadLocal.set(conn); ?? ??? ? ? ? ? ? ? return conn;?? ? ?? ??? ? ? ? ? }else {?? ?//③直接返回線程本地變量 ?? ??? ? ? ? ? ? ? return connThreadLocal.get(); ? ? ??? ? ?? ??? ? ? ? ? } ?? ? ?? ??? ? ? ?} ?? ? ?? ??? ? ? ?public void addTopic() { ?? ? ?? ??? ? ? ? ? // ④從ThreadLocal中獲取線程對(duì)應(yīng)的Connection,每個(gè)線程都保存一份獨(dú)立的變量?? ? ?? ??? ? ? ? ? try {?? ? ?? ??? ? ? ? ? ? ? Statement stat = getConnection().createStatement();?? ? ?? ??? ? ? ? ? }catch (SQLException e) {?? ? ?? ??? ? ? ? ? ? ? e.printStackTrace();?? ? ?? ??? ? ? ? ? } ?? ? ?? ??? ? ? ?} ?? ? ?? ??? ?}
到此這篇關(guān)于Spring中ThreadLocal的解析的文章就介紹到這了,更多相關(guān)Spring中的ThreadLocal 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Socket+JDBC+IO實(shí)現(xiàn)Java文件上傳下載器DEMO詳解
這篇文章主要介紹了Socket+JDBC+IO實(shí)現(xiàn)Java文件上傳下載器DEMO詳解,需要的朋友可以參考下2017-05-05Java+opencv3.2.0實(shí)現(xiàn)模板匹配
這篇文章主要為大家詳細(xì)介紹了Java+opencv3.2.0實(shí)現(xiàn)模板匹配的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-02-02Intellij IDEA 2019 最新亂碼問題及解決必殺技(必看篇)
大家在使用Intellij IDEA 的時(shí)候會(huì)經(jīng)常遇到各種亂碼問題,今天小編給大家分享一些關(guān)于Intellij IDEA 2019 最新亂碼問題及解決必殺技,感興趣的朋友跟隨小編一起看看吧2020-04-04Spring Boot緩存實(shí)戰(zhàn) EhCache示例
本篇文章主要介紹了Spring Boot緩存實(shí)戰(zhàn) EhCache示例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來(lái)看看吧2017-08-08Spring MVC Controller傳遞枚舉值的實(shí)例
這篇文章主要介紹了Spring MVC Controller傳遞枚舉值的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09詳解SpringBoot中的統(tǒng)一結(jié)果返回與統(tǒng)一異常處理
這篇文章主要將通過詳細(xì)的討論和實(shí)例演示來(lái)幫助你更好地理解和應(yīng)用Spring Boot中的統(tǒng)一結(jié)果返回和統(tǒng)一異常處理,感興趣的小伙伴可以了解下2024-03-03Java經(jīng)典設(shè)計(jì)模式之裝飾器模式解析
這篇文章主要介紹了Java經(jīng)典設(shè)計(jì)模式之裝飾器模式解析,裝飾器模式主要解決繼承關(guān)系過于復(fù)雜的問題,通過組合來(lái)替代繼承,指在不改變現(xiàn)有對(duì)象結(jié)構(gòu)的情況下,動(dòng)態(tài)地給該對(duì)象增加一些職責(zé)(即增加其額外功能)的模式,需要的朋友可以參考下2023-08-08