ThreadLocal

ThreadLocal哨鸭,直譯為“線程本地”或“本地線程”民宿,如果你真的這么認(rèn)為,那就錯(cuò)了像鸡!其實(shí)活鹰,它就是一個(gè)容器,用于存放線程的局部變量,我認(rèn)為應(yīng)該叫做 ThreadLocalVariable(線程局部變量)才對志群,真不理解為什么當(dāng)初 Sun 公司的工程師這樣命名着绷。

早在 JDK 1.2 的時(shí)代,java.lang.ThreadLocal 就誕生了锌云,它是為了解決多線程并發(fā)問題而設(shè)計(jì)的荠医,只不過設(shè)計(jì)得有些難用,所以至今沒有得到廣泛使用桑涎。其實(shí)它還是挺有用的子漩,不相信的話,我們一起來看看這個(gè)例子吧石洗。

一個(gè)序列號生成器的程序幢泼,可能同時(shí)會(huì)有多個(gè)線程并發(fā)訪問它,要保證每個(gè)線程得到的序列號都是自增的讲衫,而不能相互干擾缕棵。

先定義一個(gè)接口:

public interface Sequence {

int getNumber();

}

每次調(diào)用 getNumber() 方法可獲取一個(gè)序列號,下次再調(diào)用時(shí)涉兽,序列號會(huì)自增招驴。

再做一個(gè)線程類:

public class ClientThread extends Thread {

private Sequence sequence;

public ClientThread(Sequence sequence) {

this``.sequence = sequence;

}

@Override

public void run() {

for (``int i = 0``; i < 3``; i++) {

System.out.println(Thread.currentThread().getName() + " => " + sequence.getNumber());

}

}

}
引用地址:http://www.importnew.com/20147.html

在線程中連續(xù)輸出三次線程名與其對應(yīng)的序列號。

我們先不用 ThreadLocal枷畏,來做一個(gè)實(shí)現(xiàn)類吧别厘。

public class SequenceA implements Sequence {

private static int number = 0``;

public int getNumber() {

number = number + 1``;

return number;

}

public static void main(String[] args) {

Sequence sequence = new SequenceA();

ClientThread thread1 = new ClientThread(sequence);

ClientThread thread2 = new ClientThread(sequence);

ClientThread thread3 = new ClientThread(sequence);

thread1.start();

thread2.start();

thread3.start();

}

}

|

序列號初始值是0,在 main() 方法中模擬了三個(gè)線程拥诡,運(yùn)行后結(jié)果如下:

Thread-0 => 1
Thread-0 => 2
Thread-0 => 3
Thread-2 => 4
Thread-2 => 5
Thread-2 => 6
Thread-1 => 7
Thread-1 => 8
Thread-1 => 9

由于線程啟動(dòng)順序是隨機(jī)的触趴,所以并不是0、1渴肉、2這樣的順序冗懦,這個(gè)好理解。為什么當(dāng) Thread-0 輸出了1仇祭、2披蕉、3之后,而 Thread-2 卻輸出了4乌奇、5没讲、6呢?線程之間竟然共享了 static 變量礁苗!這就是所謂的“非線程安全”問題了爬凑。

那么如何來保證“線程安全”呢?對應(yīng)于這個(gè)案例寂屏,就是說不同的線程可擁有自己的 static 變量贰谣,如何實(shí)現(xiàn)呢娜搂?下面看看另外一個(gè)實(shí)現(xiàn)吧迁霎。

public class SequenceB implements Sequence {

private static ThreadLocal<Integer> numberContainer = new ThreadLocal<Integer>() {

@Override

protected Integer initialValue() {

return 0``;

}

};

public int getNumber() {

numberContainer.set(numberContainer.get() + 1``);

return numberContainer.get();

}

public static void main(String[] args) {

Sequence sequence = new SequenceB();

ClientThread thread1 = new ClientThread(sequence);

ClientThread thread2 = new ClientThread(sequence);

ClientThread thread3 = new ClientThread(sequence);

thread1.start();

thread2.start();

thread3.start();

}

}

|

通過 ThreadLocal 封裝了一個(gè) Integer 類型的 numberContainer 靜態(tài)成員變量吱抚,并且初始值是0。再看 getNumber() 方法考廉,首先從 numberContainer 中 get 出當(dāng)前的值秘豹,加1,隨后 set 到 numberContainer 中昌粤,最后將 numberContainer 中 get 出當(dāng)前的值并返回既绕。

是不是很惡心?但是很強(qiáng)大涮坐!確實(shí)稍微饒了一下凄贩,我們不妨把 ThreadLocal 看成是一個(gè)容器,這樣理解就簡單了袱讹。所以疲扎,這里故意用 Container 這個(gè)單詞作為后綴來命名 ThreadLocal 變量。

運(yùn)行結(jié)果如何呢捷雕?看看吧椒丧。

Thread-0 => 1
Thread-0 => 2
Thread-0 => 3
Thread-2 => 1
Thread-2 => 2
Thread-2 => 3
Thread-1 => 1
Thread-1 => 2
Thread-1 => 3

每個(gè)線程相互獨(dú)立了,同樣是 static 變量救巷,對于不同的線程而言壶熏,它沒有被共享,而是每個(gè)線程各一份浦译,這樣也就保證了線程安全棒假。 也就是說,TheadLocal 為每一個(gè)線程提供了一個(gè)獨(dú)立的副本精盅!

搞清楚 ThreadLocal 的原理之后淆衷,有必要總結(jié)一下 ThreadLocal 的 API,其實(shí)很簡單渤弛。

  1. public void set(T value):將值放入線程局部變量中
  2. public T get():從線程局部變量中獲取值
  3. public void remove():從線程局部變量中移除值(有助于 JVM 垃圾回收)
  4. protected T initialValue():返回線程局部變量中的初始值(默認(rèn)為 null)

為什么 initialValue() 方法是 protected 的呢祝拯?就是為了提醒程序員們,這個(gè)方法是要你們來實(shí)現(xiàn)的她肯,請給這個(gè)線程局部變量一個(gè)初始值吧佳头。

了解了原理與這些 API,其實(shí)想想 ThreadLocal 里面不就是封裝了一個(gè) Map 嗎晴氨?自己都可以寫一個(gè) ThreadLocal 了康嘉,嘗試一下吧。

public class MyThreadLocal<T> {

private Map<Thread, T> container = Collections.synchronizedMap(``new HashMap<Thread, T>());

public void set(T value) {

container.put(Thread.currentThread(), value);

}

public T get() {

Thread thread = Thread.currentThread();

T value = container.get(thread);

if (value == null && !container.containsKey(thread)) {

value = initialValue();

container.put(thread, value);

}

return value;

}

public void remove() {

container.remove(Thread.currentThread());

}

protected T initialValue() {

return null``;

}

}

|

以上完全山寨了一個(gè) ThreadLocal籽前,其中中定義了一個(gè)同步 Map(為什么要這樣亭珍?請讀者自行思考)敷钾,代碼應(yīng)該非常容易讀懂。

下面用這 MyThreadLocal 再來實(shí)現(xiàn)一把看看肄梨。

public class SequenceC implements Sequence {

private static MyThreadLocal<Integer> numberContainer = new MyThreadLocal<Integer>() {

@Override

protected Integer initialValue() {

return 0``;

}

};

public int getNumber() {

numberContainer.set(numberContainer.get() + 1``);

return numberContainer.get();

}

public static void main(String[] args) {

Sequence sequence = new SequenceC();

ClientThread thread1 = new ClientThread(sequence);

ClientThread thread2 = new ClientThread(sequence);

ClientThread thread3 = new ClientThread(sequence);

thread1.start();

thread2.start();

thread3.start();

}

}

|

以上代碼其實(shí)就是將 ThreadLocal 替換成了 MyThreadLocal阻荒,僅此而已,運(yùn)行效果和之前的一樣众羡,也是正確的侨赡。

其實(shí) ThreadLocal 可以單獨(dú)成為一種設(shè)計(jì)模式,就看你怎么看了粱侣。

ThreadLocal 具體有哪些使用案例呢羊壹?

我想首先要說的就是:通過 ThreadLocal 存放 JDBC Connection,以達(dá)到事務(wù)控制的能力齐婴。

如何實(shí)現(xiàn)呢油猫?下回分解!

ThreadLocal 那點(diǎn)事兒(續(xù)集)

注意:當(dāng)您在一個(gè)類中使用了 static 成員變量的時(shí)候柠偶,一定要多問問自己情妖,這個(gè) static 成員變量需要考慮“線程安全”嗎?(也就是說嚣州,多個(gè)線程需要獨(dú)享自己的 static 成員變量嗎鲫售?)如果需要考慮,那就請用 ThreadLocal 吧该肴!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末情竹,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子匀哄,更是在濱河造成了極大的恐慌秦效,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件涎嚼,死亡現(xiàn)場離奇詭異阱州,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)法梯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門苔货,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人立哑,你說我怎么就攤上這事夜惭。” “怎么了铛绰?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵诈茧,是天一觀的道長。 經(jīng)常有香客問我捂掰,道長敢会,這世上最難降的妖魔是什么曾沈? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮鸥昏,結(jié)果婚禮上塞俱,老公的妹妹穿的比我還像新娘。我一直安慰自己互广,他們只是感情好敛腌,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布卧土。 她就那樣靜靜地躺著惫皱,像睡著了一般。 火紅的嫁衣襯著肌膚如雪尤莺。 梳的紋絲不亂的頭發(fā)上旅敷,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機(jī)與錄音颤霎,去河邊找鬼媳谁。 笑死,一個(gè)胖子當(dāng)著我的面吹牛友酱,可吹牛的內(nèi)容都是我干的晴音。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼缔杉,長吁一口氣:“原來是場噩夢啊……” “哼锤躁!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起或详,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤系羞,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后霸琴,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體椒振,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年梧乘,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了澎迎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,039評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡选调,死狀恐怖夹供,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情学歧,我是刑警寧澤罩引,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站枝笨,受9級特大地震影響袁铐,放射性物質(zhì)發(fā)生泄漏揭蜒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一剔桨、第九天 我趴在偏房一處隱蔽的房頂上張望屉更。 院中可真熱鬧,春花似錦洒缀、人聲如沸瑰谜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽萨脑。三九已至,卻和暖如春饺饭,著一層夾襖步出監(jiān)牢的瞬間渤早,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工瘫俊, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鹊杖,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓扛芽,卻偏偏與公主長得像骂蓖,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子川尖,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評論 2 345

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

  • Android Handler機(jī)制系列文章整體內(nèi)容如下: Android Handler機(jī)制1之ThreadAnd...
    隔壁老李頭閱讀 7,607評論 4 30
  • 前言 ThreadLocal很多同學(xué)都搞不懂是什么東西登下,可以用來干嘛。但面試時(shí)卻又經(jīng)常問到空厌,所以這次我和大家一起學(xué)...
    liangzzz閱讀 12,423評論 14 228
  • ThreadLocal和線程同步機(jī)制相比:都是為了解決多線程中相同變量的訪問沖突問題庐船。在同步機(jī)制中,通過對象的鎖機(jī)...
    tiancijiaren閱讀 375評論 0 1
  • 前言: 相信讀者在網(wǎng)上也看了很多關(guān)于ThreadLocal的資料嘲更,很多博客都這樣說:ThreadLoca...
    Coder_L閱讀 408評論 0 0
  • 肇俊哲 你是中國的薩內(nèi)蒂 筐钟,遼小虎們,險(xiǎn)些上演了凱特斯勞滕傳奇赋朦,那里有你篓冲。 而中國在世界杯上的記憶里,只剩下了你的...
    潔汝大弟閱讀 259評論 2 15