ThreadLocal的原理及用法

一溜哮、ThreadLocal原理

簡單說 ThreadLocal 就是一種以空間換時間的做法,在每個 Thread 里面維護(hù)了一個以開放定址法實現(xiàn)的ThreadLocal.ThreadLocalMap圈澈,把數(shù)據(jù)進(jìn)行隔離,數(shù)據(jù)不共享尘惧,自然就沒有線程安全方面的問題了康栈。

JDK1.2 就提供了java.lang.ThreadLocal。ThreadLocal 為解決多線程程序的并發(fā)問題提供了一種新的思路喷橙。使用這個工具類可以很簡潔地編寫出優(yōu)美的多線程程序啥么,ThreadLocal 并不是一個 Thread,而是 Thread 的局部變量贰逾。

ThreadLocal 用于保存某個線程共享變量:對于同一個 static ThreadLocal悬荣,其為每個使用該變量的線程提供獨立的變量副本,不同線程只能從中 get疙剑、set 和 remove 自己的變量氯迂,所以每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應(yīng)的副本言缤。從線程的角度看嚼蚀,目標(biāo)變量就像是線程的本地變量,這也是類名中“Local”所要表達(dá)的意思管挟。ThreadLocal 類接口很簡單轿曙,只有四個方法:

  1. void set(Object value)設(shè)置當(dāng)前線程的線程局部變量的值。
  2. public Object get()返回當(dāng)前線程所對應(yīng)的線程局部變量僻孝。
  3. public void remove()將當(dāng)前線程局部變量的值刪除导帝,目的是為了減少內(nèi)存的占用,該方法是 JDK5.0 新增的方法穿铆。需要指出的是您单,當(dāng)線程結(jié)束后,對應(yīng)該線程的局部變量將自動被垃圾回收悴务,所以顯式調(diào)用該方法清除線程的局部變量并不是必須的操作睹限,但它可以加快內(nèi)存回收的速度。
  4. protected Object initialValue()返回該線程局部變量的初始值讯檐,該方法是一個 protected 的方法羡疗,顯然是為了讓子類覆蓋而設(shè)計的。這個方法是一個延遲調(diào)用方法别洪,在線程第 1 次調(diào)用 get() 或 set(Object) 時才執(zhí)行叨恨,并且僅執(zhí)行 1 次。ThreadLocal 中的缺省實現(xiàn)直接返回一個 null挖垛。

值得一提的是痒钝,在 JDK5.0 中秉颗,ThreadLocal 已經(jīng)支持泛型,該類的類名已經(jīng)變?yōu)?ThreadLocal<T>送矩。API 方法也相應(yīng)進(jìn)行了調(diào)整蚕甥,新版本的 API 方法分別是 void set(T value)、T get() 以及 T initialValue()栋荸。

ThreadLocal 如何為每一個線程維護(hù)變量的副本菇怀?思路很簡單:在 ThreadLocal 類中有一個 Map,用于存儲每一個線程的變量副本晌块,Map 中元素的鍵為線程對象爱沟,而值對應(yīng)線程的變量副本。

二匆背、用法

1??線程共享變量緩存如下:Thread.ThreadLocalMap<ThreadLocal, Object>;

  1. Thread:當(dāng)前線程呼伸,可以通過 Thread.currentThread() 獲取。
  2. ThreadLocal:static ThreadLocal 變量钝尸。
  3. Object:當(dāng)前線程共享變量括享。

調(diào)用 ThreadLocal.get() 時,實際上是從當(dāng)前線程中獲取 ThreadLocalMap<ThreadLocal, Object>蝶怔,然后根據(jù)當(dāng)前 ThreadLocal 獲取當(dāng)前線程共享變量 Object奶浦。ThreadLocal.set兄墅,ThreadLocal.remove 實際上是同樣的道理踢星。

2??這種存儲結(jié)構(gòu)的好處:

  1. 線程死去的時候,線程共享變量 ThreadLocalMap 則銷毀隙咸。
  2. ThreadLocalMap<ThreadLocal,Object> 鍵值對數(shù)量為 ThreadLocal 的數(shù)量沐悦,一般來說 ThreadLocal 數(shù)量很少。相比在 ThreadLocal 中用 Map<Thread, Object> 鍵值對存儲線程共享變量(Thread 數(shù)量一般來說比 ThreadLocal 數(shù)量多)五督,性能提高很多藏否。

3??關(guān)于ThreadLocalMap<ThreadLocal, Object>弱引用問題:

當(dāng)線程沒有結(jié)束,但是 ThreadLocal 已經(jīng)被回收充包,則可能導(dǎo)致線程中存在ThreadLocalMap<null, Object> 的鍵值對副签,造成內(nèi)存泄露。(ThreadLocal 被回收基矮,ThreadLocal 關(guān)聯(lián)的線程共享變量還存在淆储。)

雖然 ThreadLocal 的 get/set 方法可以清除 ThreadLocalMap 中 key 為 null 的 value,但是 get/set 方法在內(nèi)存泄露后并不會必然調(diào)用家浇,所以為了防止此類情況的出現(xiàn)本砰,有兩種手段:

  1. 使用完線程共享變量后,顯示調(diào)用 ThreadLocalMap.remove 方法清除線程共享變量钢悲。
  2. JDK 建議 ThreadLocal 定義為 private static点额,這樣 ThreadLocal 的弱引用問題則不存在了舔株。
//ThreadLocal用法
public class MyThreadLocal {
    private static final ThreadLocal<Object> threadLocal = new ThreadLocal<Object>() {
    //ThreadLocal沒有被當(dāng)前線程賦值時或當(dāng)前線程剛調(diào)用remove方法后調(diào)用get方法,返回此方法值
        @Override
        protected Object initialValue() {
            System.out.println("調(diào)用get()時还棱,當(dāng)前線程共享變量沒有設(shè)置载慈,調(diào)initialValue獲取默認(rèn)值");
            return null;
        }
    };

    public static void main(String[] args) {
        new Thread(new MyIntegerTask("IntegerTask1")).start();
        new Thread(new MyStringTask("StringTask1")).start();
        new Thread(new MyIntegerTask("IntegerTask2")).start();
        new Thread(new MyStringTask("StringTask2")).start();
    }

    public static class MyIntegerTask implements Runnable {
        private String name;
        MyIntegerTask(String name) {
            this.name = name;
        }
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                // ThreadLocal.get方法獲取線程變量
                if (null == MyThreadLocal.threadLocal.get()) {
                    // ThreadLocal.et方法設(shè)置線程變量
                    MyThreadLocal.threadLocal.set(0);
                    System.out.println("線程" + name + ": 0");
                } else {
                    int num = (Integer) MyThreadLocal.threadLocal.get();
                    MyThreadLocal.threadLocal.set(num + 1);
                    System.out.println("線程" + name + ":" + MyThreadLocal.threadLocal.get());
                    if (i == 3) {
                        MyThreadLocal.threadLocal.remove();
                    }
                }
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static class MyStringTask implements Runnable {
        private String name;
        MyStringTask(String name) {
            this.name = name;
        }
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                if (null == MyThreadLocal.threadLocal.get()) {
                    MyThreadLocal.threadLocal.set("a");
                    System.out.println("線程" + name + ":a");
                } else {
                    String str = (String) MyThreadLocal.threadLocal.get();
                    MyThreadLocal.threadLocal.set(str + "a");
                    System.out.println("線程" + name + ":" + MyThreadLocal.threadLocal.get());
                }
                try {
                    Thread.sleep(800);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

結(jié)果如下:

調(diào)用get方法時,當(dāng)前線程共享變量沒有設(shè)置珍手,調(diào)用initialValue獲取默認(rèn)值娃肿!
線程IntegerTask1: 0
調(diào)用get方法時,當(dāng)前線程共享變量沒有設(shè)置珠十,調(diào)用initialValue獲取默認(rèn)值料扰!
調(diào)用get方法時,當(dāng)前線程共享變量沒有設(shè)置焙蹭,調(diào)用initialValue獲取默認(rèn)值晒杈!
線程IntegerTask2: 0
線程StringTask1: a
調(diào)用get方法時,當(dāng)前線程共享變量沒有設(shè)置孔厉,調(diào)用initialValue獲取默認(rèn)值拯钻!
線程StringTask2: a
線程StringTask1: aa
線程StringTask2: aa
線程IntegerTask1: 1
線程IntegerTask2: 1
線程StringTask1: aaa
線程StringTask2: aaa
線程IntegerTask1: 2
線程IntegerTask2: 2
線程StringTask2: aaaa
線程StringTask1: aaaa
線程IntegerTask1: 3
線程IntegerTask2: 3
線程StringTask1: aaaaa
線程StringTask2: aaaaa
調(diào)用get方法時,當(dāng)前線程共享變量沒有設(shè)置撰豺,調(diào)用initialValue獲取默認(rèn)值粪般!
線程IntegerTask1: 0
調(diào)用get方法時,當(dāng)前線程共享變量沒有設(shè)置污桦,調(diào)用initialValue獲取默認(rèn)值亩歹!
線程IntegerTask2: 0
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市凡橱,隨后出現(xiàn)的幾起案子小作,更是在濱河造成了極大的恐慌,老刑警劉巖稼钩,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件顾稀,死亡現(xiàn)場離奇詭異,居然都是意外死亡坝撑,警方通過查閱死者的電腦和手機(jī)静秆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來巡李,“玉大人抚笔,你說我怎么就攤上這事』骼埽” “怎么了塔沃?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我蛀柴,道長螃概,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任鸽疾,我火速辦了婚禮吊洼,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘制肮。我一直安慰自己冒窍,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布豺鼻。 她就那樣靜靜地躺著综液,像睡著了一般。 火紅的嫁衣襯著肌膚如雪儒飒。 梳的紋絲不亂的頭發(fā)上谬莹,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天,我揣著相機(jī)與錄音桩了,去河邊找鬼附帽。 笑死,一個胖子當(dāng)著我的面吹牛井誉,可吹牛的內(nèi)容都是我干的蕉扮。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼颗圣,長吁一口氣:“原來是場噩夢啊……” “哼喳钟!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起欠啤,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤荚藻,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后洁段,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡共郭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年祠丝,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片除嘹。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡写半,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出尉咕,到底是詐尸還是另有隱情叠蝇,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布年缎,位于F島的核電站悔捶,受9級特大地震影響铃慷,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蜕该,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一犁柜、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧堂淡,春花似錦馋缅、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至皆的,卻和暖如春稚疹,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背祭务。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工内狗, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人义锥。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓柳沙,卻偏偏與公主長得像,于是被迫代替她去往敵國和親拌倍。 傳聞我的和親對象是個殘疾皇子赂鲤,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,762評論 2 345