ThreadLocal使用場景分析

ThreadLocal<T>其實是與線程綁定的一個變量。ThreadLocal和Synchonized都用于解決多線程并發(fā)訪問黄痪。但是ThreadLocal與synchronized有本質(zhì)的區(qū)別钢猛。Synchronized用于線程間的數(shù)據(jù)共享缅叠,而ThreadLocal則用于線程間的數(shù)據(jù)隔離逆航。Synchronized是利用鎖的機制穴豫,使變量或代碼塊在某一時該只能被一個線程訪問怀大。而ThreadLocal為每一個線程都提供了變量的副本纱兑,使得每個線程在某一時間訪問到的并不是同一個對象,這樣就隔離了多個線程對數(shù)據(jù)的數(shù)據(jù)共享化借。而Synchronized卻正好相反潜慎,它用于在多個線程間通信時能夠獲得數(shù)據(jù)共享。

一句話理解ThreadLocal蓖康,向ThreadLocal里面存東西就是向它里面的Map存東西的铐炫,然后ThreadLocal把這個Map掛到當(dāng)前的線程底下,這樣Map就只屬于這個線程了蒜焊。

ThreadLocal源碼

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

    public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

    // 通過靜態(tài)內(nèi)部類實現(xiàn)變量與線程綁定
    static class ThreadLocalMap {...}

ThreadLocal其實是與線程綁定的一個變量倒信,如此就會出現(xiàn)一個問題:如果沒有將ThreadLocal內(nèi)的變量刪除(remove)或替換,它的生命周期將會與線程共存泳梆。通常線程池中對線程管理都是采用線程復(fù)用的方法鳖悠,在線程池中線程很難結(jié)束甚至于永遠(yuǎn)不會結(jié)束,這將意味著線程持續(xù)的時間將不可預(yù)測优妙,甚至與JVM的生命周期一致乘综。舉個例字,如果ThreadLocal中直接或間接包裝了集合類或復(fù)雜對象鳞溉,每次在同一個ThreadLocal中取出對象后瘾带,再對內(nèi)容做操作鼠哥,那么內(nèi)部的集合類和復(fù)雜對象所占用的空間可能會開始持續(xù)膨脹熟菲。

How to use ThreadLocal

Spring使用ThreadLocal解決線程安全問題看政。通常只有無狀態(tài)的Bean才可以在多線程環(huán)境下共享,在Spring中抄罕,絕大部分Bean都可以聲明為singleton作用域允蚣。就是因為Spring對一些Bean(如RequestContextHolder、TransactionSynchronizationManager呆贿、LocaleContextHolder等)中非線程安全的“狀態(tài)性對象”采用ThreadLocal進行封裝嚷兔,讓它們也成為線程安全的“狀態(tài)性對象”,因此有狀態(tài)的Bean就能夠以singleton的方式在多線程中正常工作了做入。一般的Web應(yīng)用劃分為控制層冒晰、服務(wù)層和持久層三個層次,在不同的層中編寫對應(yīng)的邏輯竟块,下層通過接口向上層開放功能調(diào)用壶运。在一般情況下,從接收請求到返回響應(yīng)所經(jīng)過的所有程序調(diào)用都同屬于一個線程浪秘。這樣用戶就可以根據(jù)需要蒋情,將一些非線程安全的變量以ThreadLocal存放,在同一次請求響應(yīng)的調(diào)用線程中耸携,所有對象所訪問的同一ThreadLocal變量都是當(dāng)前線程所綁定的棵癣。

// 非線程安全
public class TopicDao {
   //①一個非線程安全的變量
   private Connection conn; 
   public void addTopic(){
        //②引用非線程安全變量
       Statement stat = conn.createStatement();
       …
   }
}

由于①處的conn是成員變量,因為addTopic()方法是非線程安全的夺衍,必須在使用時創(chuàng)建一個新TopicDao實例(非singleton)狈谊。下面使用ThreadLocal對conn這個非線程安全的“狀態(tài)”進行改造:

import java.sql.Connection;
import java.sql.Statement;
public class TopicDao {

  //①使用ThreadLocal保存Connection變量
private static ThreadLocal<Connection> connThreadLocal = new ThreadLocal<Connection>();
public static Connection getConnection(){
         
        //②如果connThreadLocal沒有本線程對應(yīng)的Connection創(chuàng)建一個新的Connection,
        //并將其保存到線程本地變量中沟沙。
if (connThreadLocal.get() == null) {
            Connection conn = ConnectionManager.getConnection();
            connThreadLocal.set(conn);
              return conn;
        }else{
              //③直接返回線程本地變量
            return connThreadLocal.get();
        }
    }
    public void addTopic() {

        //④從ThreadLocal中獲取線程對應(yīng)的
         Statement stat = getConnection().createStatement();
    }
}

不同的線程在使用TopicDao時的畴,先判斷connThreadLocal.get()是否為null,如果為null尝胆,則說明當(dāng)前線程還沒有對應(yīng)的Connection對象丧裁,這時創(chuàng)建一個Connection對象并添加到本地線程變量中;如果不為null含衔,則說明當(dāng)前的線程已經(jīng)擁有了Connection對象盹憎,直接使用就可以了。這樣乾颁,就保證了不同的線程使用線程相關(guān)的Connection棚品,而不會使用其他線程的Connection。因此杭隙,這個TopicDao就可以做到singleton共享了哟绊。 當(dāng)然,這個例子本身很粗糙痰憎,將Connection的ThreadLocal直接放在Dao只能做到本Dao的多個方法共享Connection時不發(fā)生線程安全問題票髓,但無法和其他Dao共用同一個Connection攀涵,要做到同一事務(wù)多Dao共享同一個Connection,必須在一個共同的外部類使用ThreadLocal保存Connection洽沟。但這個實例基本上說明了Spring對有狀態(tài)類線程安全化的解決思路以故。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市裆操,隨后出現(xiàn)的幾起案子怒详,更是在濱河造成了極大的恐慌,老刑警劉巖踪区,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件昆烁,死亡現(xiàn)場離奇詭異,居然都是意外死亡缎岗,警方通過查閱死者的電腦和手機善玫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來密强,“玉大人茅郎,你說我怎么就攤上這事』虿常” “怎么了系冗?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長薪鹦。 經(jīng)常有香客問我掌敬,道長,這世上最難降的妖魔是什么池磁? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任奔害,我火速辦了婚禮,結(jié)果婚禮上地熄,老公的妹妹穿的比我還像新娘华临。我一直安慰自己,他們只是感情好端考,可當(dāng)我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布雅潭。 她就那樣靜靜地躺著,像睡著了一般却特。 火紅的嫁衣襯著肌膚如雪扶供。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天裂明,我揣著相機與錄音椿浓,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛扳碍,可吹牛的內(nèi)容都是我干的提岔。 我是一名探鬼主播,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼左腔,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了捅儒?” 一聲冷哼從身側(cè)響起液样,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎巧还,沒想到半個月后鞭莽,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡麸祷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年澎怒,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片阶牍。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡喷面,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出走孽,到底是詐尸還是另有隱情惧辈,我是刑警寧澤,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布磕瓷,位于F島的核電站盒齿,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏困食。R本人自食惡果不足惜边翁,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望硕盹。 院中可真熱鬧符匾,春花似錦、人聲如沸瘩例。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽仰剿。三九已至创淡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間南吮,已是汗流浹背琳彩。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人露乏。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓碧浊,卻偏偏與公主長得像,于是被迫代替她去往敵國和親瘟仿。 傳聞我的和親對象是個殘疾皇子箱锐,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,601評論 2 353

推薦閱讀更多精彩內(nèi)容