Java多線程:帶你了解神秘的線程變量 ThreadLocal

前言

在 Java多線程中,線程變量ThreadLocal非常重要漂辐,但對于很多開發(fā)者來說,這并不容易理解棕硫,甚至覺得有點神秘

今天髓涯,我將獻上一份?ThreadLocal的介紹 & 實戰(zhàn)攻略,希望你們會喜歡哈扮。

目錄

1. 簡介

我自己是一個從事了6年的Java全棧工程師纬纪,最近整理了一套適合2019年學(xué)習(xí)的Java\大數(shù)據(jù)資料,從基礎(chǔ)的Java滑肉、大數(shù)據(jù)面向?qū)ο蟮竭M階的框架知識都有整理哦包各,可以來我的主頁免費領(lǐng)取哦。

2. 使用流程

主要是創(chuàng)建ThreadLocal變量 & 訪問ThreadLocal變量

2.1 創(chuàng)建ThreadLocal變量

共有3種方式靶庙,具體如下

// 1. 直接創(chuàng)建對象

private ThreadLocal myThreadLocal = new ThreadLocal()

// 2. 創(chuàng)建泛型對象

private ThreadLocal myThreadLocal = new ThreadLocal();

// 3. 創(chuàng)建泛型對象 & 初始化值

// 指定泛型的好處:不需要每次對使用get()方法返回的值作強制類型轉(zhuǎn)換

private ThreadLocal myThreadLocal = new ThreadLocal() {

@Override

protected String initialValue() {

return "This is the initial value";

}

};

// 特別注意:

// 1. ThreadLocal實例 = 類中的private问畅、static字段

// 2. 只需實例化對象一次 & 不需知道它是被哪個線程實例化

// 3. 每個線程都保持 對其線程局部變量副本 的隱式引用

// 4. 線程消失后,其線程局部實例的所有副本都會被垃圾回收(除非存在對這些副本的其他引用)

// 5. 雖然所有的線程都能訪問到這個ThreadLocal實例六荒,但是每個線程只能訪問到自己通過調(diào)用ThreadLocal的set()設(shè)置的值

// 即 哪怕2個不同的線程在同一個`ThreadLocal`對象上設(shè)置了不同的值护姆,他們?nèi)匀粺o法訪問到對方的值

2.2 訪問ThreadLocal變量

// 1. 設(shè)置值:set()

// 需要傳入一個Object類型的參數(shù)

myThreadLocal.set("初始值”);

// 2. 讀取ThreadLocal變量中的值:get()

// 返回一個Object對象

String threadLocalValue = (String) myThreadLocal.get();

3. 具體使用

以下則是測試代碼

public class ThreadLocalTest {

// 測試代碼

public static void main(String[] args){

// 新開2個線程用于設(shè)置 & 獲取 ThreadLoacl的值

MyRunnable runnable = new MyRunnable();

new Thread(runnable, "線程1").start();

new Thread(runnable, "線程2").start();

}

// 線程類

public static class MyRunnable implements Runnable {

// 創(chuàng)建ThreadLocal & 初始化

private ThreadLocal threadLocal = new ThreadLocal(){

@Override

protected String initialValue() {

return "初始化值";

}

};

@Override

public void run() {

// 運行線程時,分別設(shè)置 & 獲取 ThreadLoacl的值

String name = Thread.currentThread().getName();

threadLocal.set(name + "的threadLocal"); // 設(shè)置值 = 線程名

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println(name + ":" + threadLocal.get());

}

}

}

測試結(jié)果

線程1:線程1的threadLocal

線程2:線程2的threadLocal

// 從上述結(jié)果看出掏击,在2個線程分別設(shè)置ThreadLocal值 & 分別獲取卵皂,結(jié)果并未互相干擾

4. 實現(xiàn)原理

核心原理

ThreadLocal類中有1個Map(稱:ThreadLocalMap):用于存儲每個線程 & 該線程設(shè)置的存儲在ThreadLocal變量的值

ThreadLocalMap的鍵Key = 當(dāng)前ThreadLocal實例、值value = 該線程設(shè)置的存儲在ThreadLocal變量的值

該key是 ThreadLocal對象的弱引用砚亭;當(dāng)要拋棄掉ThreadLocal對象時灯变,垃圾收集器會忽略該 key的引用而清理掉ThreadLocal對象

關(guān)于如何設(shè)置 & 獲取 ThreadLocal變量里的值,具體請看下面的源碼分析

請直接看代碼注釋

// ThreadLocal的源碼

public class ThreadLocal {

...

/**

* 設(shè)置ThreadLocal變量引用的值

* ThreadLocal變量引用 指向 ThreadLocalMap對象捅膘,即設(shè)置ThreadLocalMap的值 = 該線程設(shè)置的存儲在ThreadLocal變量的值

* ThreadLocalMap的鍵Key = 當(dāng)前ThreadLocal實例

* ThreadLocalMap的值 = 該線程設(shè)置的存儲在ThreadLocal變量的值

**/

public void set(T value) {

// 1. 獲得當(dāng)前線程

Thread t = Thread.currentThread();

// 2. 獲取該線程的ThreadLocalMap對象 ->>分析1

ThreadLocalMap map = getMap(t);

// 3. 若該線程的ThreadLocalMap對象已存在添祸,則替換該Map里的值;否則創(chuàng)建1個ThreadLocalMap對象

if (map != null)

map.set(this, value);// 替換

else

createMap(t, value);// 創(chuàng)建->>分析2

}

/**

* 獲取ThreadLocal變量里的值

* 由于ThreadLocal變量引用 指向 ThreadLocalMap對象寻仗,即獲取ThreadLocalMap對象的值 = 該線程設(shè)置的存儲在ThreadLocal變量的值

**/

public T get() {

// 1. 獲得當(dāng)前線程

Thread t = Thread.currentThread();

// 2. 獲取該線程的ThreadLocalMap對象

ThreadLocalMap map = getMap(t);

// 3. 若該線程的ThreadLocalMap對象已存在膝捞,則直接獲取該Map里的值;否則則通過初始化函數(shù)創(chuàng)建1個ThreadLocalMap對象

if (map != null) {

ThreadLocalMap.Entry e = map.getEntry(this);

if (e != null)

return (T)e.value; // 直接獲取值

}

return setInitialValue(); // 初始化

}

/**

* 初始化ThreadLocal的值

**/

private T setInitialValue() {

T value = initialValue();

// 1. 獲得當(dāng)前線程

Thread t = Thread.currentThread();

// 2. 獲取該線程的ThreadLocalMap對象

ThreadLocalMap map = getMap(t);

// 3. 若該線程的ThreadLocalMap對象已存在,則直接替換該值蔬咬;否則則創(chuàng)建

if (map != null)

map.set(this, value); // 替換

else

createMap(t, value); // 創(chuàng)建->>分析2

return value;

}

/**

* 分析1:獲取當(dāng)前線程的threadLocals變量引用

**/

ThreadLocalMap getMap(Thread t) {

return t.threadLocals;

}

/**

* 分析2:創(chuàng)建當(dāng)前線程的ThreadLocalMap對象

**/

void createMap(Thread t, T firstValue) {

// 新創(chuàng)建1個ThreadLocalMap對象 放入到 Thread類的threadLocals變量引用中:

// a. ThreadLocalMap的鍵Key = 當(dāng)前ThreadLocal實例

// b. ThreadLocalMap的值 = 該線程設(shè)置的存儲在ThreadLocal變量的值

t.threadLocals = new ThreadLocalMap(this, firstValue);

// 即 threadLocals變量 屬于 Thread類中 ->> 分析3

}

...

}

/**

* 分析3:Thread類 源碼分析

**/

public class Thread implements Runnable {

...

ThreadLocal.ThreadLocalMap threadLocals = null;

// 即 Thread類持有threadLocals變量

// 線程類實例化后鲤遥,每個線程對象擁有獨立的threadLocals變量變量

// threadLocals變量在 ThreadLocal對象中 通過set() 或 get()進行操作

...

}

5. 額外補充

5.1 ThreadLocal如何做到線程安全

每個線程擁有自己獨立的ThreadLocals變量(指向ThreadLocalMap對象 )

每當(dāng)線程 訪問 ThreadLocals變量時,訪問的都是各自線程自己的ThreadLocalMap變量(鍵 - 值)

ThreadLocalMap變量的鍵 key = 唯一 = 當(dāng)前ThreadLocal實例

上述3點 保證了線程間的數(shù)據(jù)訪問隔離林艘,即線程安全

測試代碼

public class ThreadLocalTest {

// 測試代碼

public static void main(String[] args){

// 新開2個線程用于設(shè)置 & 獲取 ThreadLoacl的值

MyRunnable runnable = new MyRunnable();

new Thread(runnable, "線程1").start();

new Thread(runnable, "線程2").start();

}

// 線程類

public static class MyRunnable implements Runnable {

// 創(chuàng)建ThreadLocal & 初始化

private ThreadLocal threadLocal = new ThreadLocal(){

@Override

protected String initialValue() {

return "初始化值";

}

};

@Override

public void run() {

// 運行線程時盖奈,分別設(shè)置 & 獲取 ThreadLoacl的值

String name = Thread.currentThread().getName();

threadLocal.set(name + "的threadLocal"); // 設(shè)置值 = 線程名

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println(name + ":" + threadLocal.get());

}

}

}

測試結(jié)果

線程1:線程1的threadLocal

線程2:線程2的threadLocal

// 從上述結(jié)果看出,在2個線程分別設(shè)置ThreadLocal值 & 分別獲取狐援,結(jié)果并未互相干擾

5.2 與同步機制的區(qū)別

我自己是一個從事了6年的Java全棧工程師钢坦,最近整理了一套適合2019年學(xué)習(xí)的Java\大數(shù)據(jù)資料,從基礎(chǔ)的Java啥酱、大數(shù)據(jù)面向?qū)ο蟮竭M階的框架知識都有整理哦爹凹,可以來我的主頁免費領(lǐng)取哦。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末镶殷,一起剝皮案震驚了整個濱河市禾酱,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌绘趋,老刑警劉巖颤陶,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異陷遮,居然都是意外死亡滓走,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門帽馋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來搅方,“玉大人,你說我怎么就攤上這事绽族⊙” “怎么了?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵项秉,是天一觀的道長绣溜。 經(jīng)常有香客問我,道長娄蔼,這世上最難降的妖魔是什么怖喻? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮岁诉,結(jié)果婚禮上锚沸,老公的妹妹穿的比我還像新娘。我一直安慰自己涕癣,他們只是感情好哗蜈,可當(dāng)我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般距潘。 火紅的嫁衣襯著肌膚如雪炼列。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天音比,我揣著相機與錄音俭尖,去河邊找鬼。 笑死洞翩,一個胖子當(dāng)著我的面吹牛稽犁,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播骚亿,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼已亥,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了来屠?” 一聲冷哼從身側(cè)響起虑椎,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎的妖,沒想到半個月后绣檬,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體足陨,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡嫂粟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了墨缘。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片星虹。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖镊讼,靈堂內(nèi)的尸體忽然破棺而出宽涌,到底是詐尸還是另有隱情,我是刑警寧澤蝶棋,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布卸亮,位于F島的核電站,受9級特大地震影響玩裙,放射性物質(zhì)發(fā)生泄漏兼贸。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一吃溅、第九天 我趴在偏房一處隱蔽的房頂上張望溶诞。 院中可真熱鬧,春花似錦决侈、人聲如沸螺垢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽枉圃。三九已至功茴,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間讯蒲,已是汗流浹背痊土。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留墨林,地道東北人赁酝。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像旭等,于是被迫代替她去往敵國和親酌呆。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,802評論 2 345

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