ThreadLocal作用湃望、場(chǎng)景、原理

1.ThreadLocal 是什么痰驱?

在JDK 1.2的版本中就提供java.lang.ThreadLocal证芭,ThreadLocal為解決多線程程序的并發(fā)問(wèn)題提供了一種新的思路。使用這個(gè)工具類可以很簡(jiǎn)潔地編寫出優(yōu)美的多線程程序担映。

ThreadLocal并不是一個(gè)Thread废士,而是Thread的局部變量,也許把它命名為ThreadLocalVariable更容易讓人理解一些蝇完。

在JDK5.0中官硝,ThreadLocal已經(jīng)支持泛型,該類的類名已經(jīng)變?yōu)門hreadLocal<T>四敞。API方法也相應(yīng)進(jìn)行了調(diào)整,新版本的API方法分別是void set(T value)拔妥、T get()以及T initialValue()忿危。

 * their normal counterparts in that each thread that accesses one (via its
 * {@code get} or {@code set} method) has its own, independently initialized
 * copy of the variable.  {@code ThreadLocal} instances are typically private
 * static fields in classes that wish to associate state with a thread (e.g.,
 * a user ID or Transaction ID)

1.1.ThreadLocal 的作用?

ThreadLocal是解決線程安全問(wèn)題一個(gè)很好的思路没龙,它通過(guò)為每個(gè)線程提供一個(gè)獨(dú)立的變量副本解決了變量并發(fā)訪問(wèn)的沖突問(wèn)題铺厨。在很多情況下,ThreadLocal比直接使用synchronized同步機(jī)制解決線程安全問(wèn)題更簡(jiǎn)單硬纤,更方便解滓,且結(jié)果程序擁有更高的并發(fā)性。

1.1.2.ThreadLocal的應(yīng)用場(chǎng)景筝家?

在Java的多線程編程中洼裤,為保證多個(gè)線程對(duì)共享變量的安全訪問(wèn),通常會(huì)使用synchronized來(lái)保證同一時(shí)刻只有一個(gè)線程對(duì)共享變量進(jìn)行操作溪王。這種情況下可以將類變量放到ThreadLocal類型的對(duì)象中腮鞍,使變量在每個(gè)線程中都有獨(dú)立拷貝,不會(huì)出現(xiàn)一個(gè)線程讀取變量時(shí)而被另一個(gè)線程修改的現(xiàn)象莹菱。最常見的ThreadLocal使用場(chǎng)景為用來(lái)解決數(shù)據(jù)庫(kù)連接移国、Session管理等。在下面會(huì)例舉幾個(gè)場(chǎng)景道伟。

2.Android源碼中也可以看到ThreadLocal的身影

這里以andorid 源碼的Handler為例子迹缀。看看Handker是怎么用ThreadLocal的。Handler就必須獲取當(dāng)前線程的 Looper 對(duì)象祝懂,而每一個(gè)線程的 Looper 是不一致的票摇。因?yàn)槊恳粋€(gè)線程都會(huì)有一個(gè) Looper 對(duì)象,因此使用 ThradLocal 去保存和獲取當(dāng)前線程的 Looper 就可以達(dá)到這個(gè)的效果嫂易。

2.1. Looper 內(nèi)部的關(guān)于在 ThreadLocal 中存儲(chǔ) Looper 和 獲取 Looper 的源碼兄朋。

//Looper.prepare();
?
private static void prepare(boolean quitAllowed) {
 if (sThreadLocal.get() != null) {
 throw new RuntimeException("Only one Looper may be created per thread");
 }
 //將創(chuàng)建的 Looper 對(duì)象保存到 sThreadLocal 中。
 sThreadLocal.set(new Looper(quitAllowed));
}
?
?
//從 ThreadLocal 取出 Looper 對(duì)象
public static @Nullable Looper myLooper() {
 return sThreadLocal.get();
}

3.ThreadLocal的內(nèi)部原理

我們從源碼中了解ThreadLocal的原理怜械,下面來(lái)看一下具體ThreadLocal是如何實(shí)現(xiàn)的颅和。

ThreadLocal類中提供了幾個(gè)方法:

1.public T get() { }

2.public void set(T value) { }

3.public void remove() { }

4.protected T initialValue(){ }

get()方法是用來(lái)獲取ThreadLocal在當(dāng)前線程中保存的變量副本,set()用來(lái)設(shè)置當(dāng)前線程中變量的副本缕允,remove()用來(lái)移除當(dāng)前線程中變量的副本峡扩,initialValue()是一個(gè)protected方法,一般是用來(lái)在使用時(shí)進(jìn)行重寫的障本,它是一個(gè)延遲加載方法教届,下面會(huì)詳細(xì)說(shuō)明。

3.1.先看下get方法源碼的實(shí)現(xiàn)

 * Returns the value in the current thread's copy of this
 * thread-local variable.  If the variable has no value for the
 * current thread, it is first initialized to the value returned
 * by an invocation of the {@link #initialValue} method.
 *
 * @return the current thread's value of this thread-local
 */
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();
}
?
/**
 * Variant of set() to establish initialValue. Used instead
 * of set() in case user has overridden the set() method.
 *
 * @return the initial value
 */
private T setInitialValue() {
 T value = initialValue();
 Thread t = Thread.currentThread();
 ThreadLocalMap map = getMap(t);
 if (map != null)
 map.set(this, value);
 else
 createMap(t, value);
 return value;
}

第一句是取得當(dāng)前線程驾霜,然后通過(guò)getMap(t)方法獲取到一個(gè)map案训,map的類型為ThreadLocalMap。然后接著下面獲取到<key,value>鍵值對(duì)粪糙,注意這里獲取鍵值對(duì)傳進(jìn)去的是 this强霎,而不是當(dāng)前線程t。 如果獲取成功蓉冈,則返回value值城舞。如果map為空,則調(diào)用setInitialValue方法返回value寞酿。

看看getMap(t)做了些什么

 * Get the map associated with a ThreadLocal. Overridden in
 * InheritableThreadLocal.
 *
 * @param  t the current thread
 * @return the map
 */
 ThreadLocalMap getMap(Thread t) {
 return t.threadLocals;
 }

在getMap中家夺,是調(diào)用當(dāng)期線程t,返回當(dāng)前線程t中的一個(gè)成員變量threadLocals伐弹。 那么我們繼續(xù)取Thread類中取看一下成員變量threadLocals是什么?繼續(xù)查看源碼

 * ThreadLocalMap is a customized hash map suitable only for
 * maintaining thread local values. No operations are exported
 * outside of the ThreadLocal class. The class is package private to
 * allow declaration of fields in class Thread.  To help deal with
 * very large and long-lived usages, the hash table entries use
 * WeakReferences for keys. However, since reference queues are not
 * used, stale entries are guaranteed to be removed only when
 * the table starts running out of space.
 */
 static class ThreadLocalMap {
?
 /**
 * The entries in this hash map extend WeakReference, using
 * its main ref field as the key (which is always a
 * ThreadLocal object).  Note that null keys (i.e. entry.get()
 * == null) mean that the key is no longer referenced, so the
 * entry can be expunged from table.  Such entries are referred to
 * as "stale entries" in the code that follows.
 */
 static class Entry extends WeakReference<ThreadLocal<?>> {
 /** The value associated with this ThreadLocal. */
 Object value;
?
 Entry(ThreadLocal<?> k, Object v) {
 super(k);
 value = v;
 }
 }
?
 //省略....
 }

實(shí)際上就是一個(gè)ThreadLocalMap拉馋,這個(gè)類型是ThreadLocal類的一個(gè)內(nèi)部類,我們繼續(xù)取看ThreadLocalMap的實(shí)現(xiàn)惨好。

再看setInitialValue()方法

setInitialValue()很容易理解椅邓,就是如果map不為空,就設(shè)置鍵值對(duì)昧狮,為空景馁,再創(chuàng)建Map,看一下createMap的實(shí)現(xiàn)逗鸣。

 * Create the map associated with a ThreadLocal. Overridden in
 * InheritableThreadLocal.
 *
 * @param t the current thread
 * @param firstValue value for the initial entry of the map
 */
 void createMap(Thread t, T firstValue) {
 t.threadLocals = new ThreadLocalMap(this, firstValue);
 }

如果使用ThreadLocal時(shí)合住,先進(jìn)行g(shù)et之前绰精,必須先set,否則會(huì)報(bào)空指針異常

public class ThreadLocalExsample {

    ThreadLocal<Long> longLocal = new ThreadLocal<>();
    public void set() {
        longLocal.set(Thread.currentThread().getId());
    }
    public long getLong() {
        return longLocal.get();
    }
 public static void main(String[] args) {
        ThreadLocalExsample test = new ThreadLocalExsample();
        //注意:沒(méi)有set之前透葛,直接get笨使,報(bào)null異常了
        System.out.println("-------threadLocal value-------" + test.getLong());
    }
}

ThreadLocal的應(yīng)用場(chǎng)景# 數(shù)據(jù)庫(kù)連接

 public Connection initialValue() {
 return DriverManager.getConnection(DB_URL);
 }
};  

public static Connection getConnection() {  
 return connectionHolder.get();
}  

ThreadLocal的應(yīng)用場(chǎng)景# Session管理

public static Session getSession() throws InfrastructureException {  
 Session s = (Session) threadSession.get();
 try {
 if (s == null) {
 s = getSessionFactory().openSession();
 threadSession.set(s);
 }
 } catch (HibernateException ex) {
 throw new InfrastructureException(ex);
 }
 return s;
}

ThreadLocal的應(yīng)用場(chǎng)景# 多線程

 * @Author 安仔夏天很勤奮
 * Create Date is  2019/3/21
 *
 * 描述 Java中的ThreadLocal類允許我們創(chuàng)建只能被同一個(gè)線程讀寫的變量。
 * 因此僚害,如果一段代碼含有一個(gè)ThreadLocal變量的引用硫椰,即使兩個(gè)線程同時(shí)執(zhí)行這段代碼,
 * 它們也無(wú)法訪問(wèn)到對(duì)方的ThreadLocal變量萨蚕。
 */
public class ThreadLocalExsample {
?
 /**
 * 創(chuàng)建了一個(gè)MyRunnable實(shí)例靶草,并將該實(shí)例作為參數(shù)傳遞給兩個(gè)線程。兩個(gè)線程分別執(zhí)行run()方法岳遥,
 * 并且都在ThreadLocal實(shí)例上保存了不同的值奕翔。如果它們?cè)L問(wèn)的不是ThreadLocal對(duì)象并且調(diào)用的set()方法被同步了,
 * 則第二個(gè)線程會(huì)覆蓋掉第一個(gè)線程設(shè)置的值浩蓉。但是派继,由于它們?cè)L問(wèn)的是一個(gè)ThreadLocal對(duì)象,
 * 因此這兩個(gè)線程都無(wú)法看到對(duì)方保存的值捻艳。也就是說(shuō)驾窟,它們存取的是兩個(gè)不同的值。
 */
 public static class MyRunnable implements Runnable {
 /**
 * 例化了一個(gè)ThreadLocal對(duì)象认轨。我們只需要實(shí)例化對(duì)象一次绅络,并且也不需要知道它是被哪個(gè)線程實(shí)例化。
 * 雖然所有的線程都能訪問(wèn)到這個(gè)ThreadLocal實(shí)例好渠,但是每個(gè)線程卻只能訪問(wèn)到自己通過(guò)調(diào)用ThreadLocal的
 * set()方法設(shè)置的值昨稼。即使是兩個(gè)不同的線程在同一個(gè)ThreadLocal對(duì)象上設(shè)置了不同的值节视,
 * 他們?nèi)匀粺o(wú)法訪問(wèn)到對(duì)方的值拳锚。
 */
 private ThreadLocal threadLocal = new ThreadLocal();
 @Override
 public void run() {
 //一旦創(chuàng)建了一個(gè)ThreadLocal變量,你可以通過(guò)如下代碼設(shè)置某個(gè)需要保存的值
 threadLocal.set((int) (Math.random() * 100D));
 try {
 Thread.sleep(2000);
 } catch (InterruptedException e) {
 }
 //可以通過(guò)下面方法讀取保存在ThreadLocal變量中的值
 System.out.println("-------threadLocal value-------"+threadLocal.get());
 }
 }
?
 public static void main(String[] args) {
 MyRunnable sharedRunnableInstance = new MyRunnable();
 Thread thread1 = new Thread(sharedRunnableInstance);
 Thread thread2 = new Thread(sharedRunnableInstance);
 thread1.start();
 thread2.start();
 }
}
?
運(yùn)行結(jié)果
-------threadLocal value-------38
-------threadLocal value-------88

得出結(jié)論

ThreadLocal 中 set 和 get 操作的都是對(duì)應(yīng)線程的 table數(shù)組寻行,因此在不同的線程中訪問(wèn)同一個(gè) ThreadLocal 對(duì)象的 set 和 get 進(jìn)行存取數(shù)據(jù)是不會(huì)相互干擾的霍掺。

總結(jié)

在每個(gè)線程Thread內(nèi)部有一個(gè)ThreadLocal.ThreadLocalMap類型的成員變量threadLocals,這個(gè)threadLocals就是用來(lái)存儲(chǔ)實(shí)際的變量副本的拌蜘,鍵值為當(dāng)前ThreadLocal變量杆烁,value為變量副本(即T類型的變量)。 初始時(shí)简卧,在Thread里面兔魂,threadLocals為空,當(dāng)通過(guò)ThreadLocal變量調(diào)用get()方法或者set()方法举娩,就會(huì)對(duì)Thread類中的threadLocals進(jìn)行初始化析校,并且以當(dāng)前ThreadLocal變量為鍵值构罗,以ThreadLocal要保存的副本變量為value,存到threadLocals智玻。 然后在當(dāng)前線程里面遂唧,如果要使用副本變量,就可以通過(guò)get方法在threadLocals里面查找吊奢。

  1. 實(shí)際的通過(guò)ThreadLocal創(chuàng)建的副本是存儲(chǔ)在每個(gè)線程自己的threadLocals中的盖彭;

  2. 為何threadLocals的類型ThreadLocalMap的鍵值為ThreadLocal對(duì)象,因?yàn)槊總€(gè)線程中可有多個(gè)threadLocal變量页滚,就像上面代碼中的longLocal和stringLocal召边;

  3. 在進(jìn)行g(shù)et之前,必須先set逻谦,否則會(huì)報(bào)空指針異常映屋;如果想在get之前不需要調(diào)用set就能正常訪問(wèn)的話,必須重寫initialValue()方法仲闽。 因?yàn)樵谏厦娴拇a分析過(guò)程中马胧,我們發(fā)現(xiàn)如果沒(méi)有先set的話,即在map中查找不到對(duì)應(yīng)的存儲(chǔ)滋将,則會(huì)通過(guò)調(diào)用setInitialValue方法返回i邻悬,而在setInitialValue方法中,有一個(gè)語(yǔ)句是T value = initialValue()随闽, 而默認(rèn)情況下父丰,initialValue方法返回的是null。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末掘宪,一起剝皮案震驚了整個(gè)濱河市蛾扇,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌魏滚,老刑警劉巖镀首,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異鼠次,居然都是意外死亡更哄,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門腥寇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)成翩,“玉大人,你說(shuō)我怎么就攤上這事赦役÷榈校” “怎么了?”我有些...
    開封第一講書人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵掂摔,是天一觀的道長(zhǎng)术羔。 經(jīng)常有香客問(wèn)我职辅,道長(zhǎng),這世上最難降的妖魔是什么聂示? 我笑而不...
    開封第一講書人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任域携,我火速辦了婚禮,結(jié)果婚禮上鱼喉,老公的妹妹穿的比我還像新娘秀鞭。我一直安慰自己,他們只是感情好扛禽,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開白布锋边。 她就那樣靜靜地躺著,像睡著了一般编曼。 火紅的嫁衣襯著肌膚如雪豆巨。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,624評(píng)論 1 305
  • 那天掐场,我揣著相機(jī)與錄音往扔,去河邊找鬼。 笑死熊户,一個(gè)胖子當(dāng)著我的面吹牛萍膛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播嚷堡,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蝗罗,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了蝌戒?” 一聲冷哼從身側(cè)響起串塑,我...
    開封第一講書人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎北苟,沒(méi)想到半個(gè)月后桩匪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡粹淋,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年吸祟,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了瑟慈。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片桃移。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖葛碧,靈堂內(nèi)的尸體忽然破棺而出借杰,到底是詐尸還是另有隱情,我是刑警寧澤进泼,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布蔗衡,位于F島的核電站纤虽,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏绞惦。R本人自食惡果不足惜逼纸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望济蝉。 院中可真熱鬧杰刽,春花似錦、人聲如沸王滤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)雁乡。三九已至第喳,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間踱稍,已是汗流浹背曲饱。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留珠月,地道東北人渔工。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像桥温,于是被迫代替她去往敵國(guó)和親引矩。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355

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