溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

關(guān)于Java多線程下 ThreadLocal 的應(yīng)用實例

發(fā)布時間:2020-04-10 12:20:31 來源:網(wǎng)絡(luò) 閱讀:152 作者:wx5d78c87dd0584 欄目:編程語言

ThreadLocal很容易讓人望文生義,想當(dāng)然地認(rèn)為是一個“本地線程” 。其實,ThreadLocal并不是一個 Thread,而是 Thread 的局部變量,也許把它命名為 ThreadLocalVariable更容易讓人理解一些。當(dāng)使用 ThreadLocal 維護變量時,ThreadLocal 為每個使用該變量的線程提供獨立的變量副本,所以每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應(yīng)的副本。
關(guān)于Java多線程下 ThreadLocal 的應(yīng)用實例

首先,ThreadLocal 不是用來解決共享對象的多線程訪問問題的,一般情況下,通過ThreadLocal.set() 到線程中的對象是該線程自己使用的對象,其他線程是不需要訪問的,也訪問不到的。各個線程中訪問的是不同的對象。

另外,說ThreadLocal使得各線程能夠保持各自獨立的一個對象,并不是通過ThreadLocal.set()來實現(xiàn)的,而是通過每個線程中的new 對象 的操作來創(chuàng)建的對象,每個線程創(chuàng)建一個,不是什么對象的拷貝或副本。通過ThreadLocal.set()將這個新創(chuàng)建的對象的引用保存到各線程的自己的一個map中,每個線程都有這樣一個map,執(zhí)行ThreadLocal.get()時,各線程從自己的map中取出放進(jìn)去的對象,因此取出來的是各自自己線程中的對象,ThreadLocal實例是作為map的key來使用的。

如果ThreadLocal.set()進(jìn)去的東西本來就是多個線程共享的同一個對象,那么多個線程的ThreadLocal.get()取得的還是這個共享對象本身,還是有并發(fā)訪問問題。

JDK 5 以后提供了泛型支持,ThreadLocal 被定義為支持泛型:

public class ThreadLocal<T> extends Object

T 為線程局部變量的類型。該類定義了 4 個方法:

1) protected T initialValue(): 返回此線程局部變量的當(dāng)前線程的“初始值”。線程第一次使用 get() 方法訪問變量時將調(diào)用此方法,但如果線程之前調(diào)用了 set(T) 方法,則不會對該線程再調(diào)用 initialValue 方法。通常,此方法對每個線程最多調(diào)用一次,但如果在調(diào)用 get() 后又調(diào)用了 remove(),則可能再次調(diào)用此方法。 該實現(xiàn)返回 null;如果程序員希望線程局部變量具有 null 以外的值,則必須為 ThreadLocal 創(chuàng)建子類,并重寫此方法。通常將使用匿名內(nèi)部類完成此操作。

2)public T get():返回此線程局部變量的當(dāng)前線程副本中的值。如果變量沒有用于當(dāng)前線程的值,則先將其初始化為調(diào)用 initialValue() 方法返回的值。

3)public void set(T value):將此線程局部變量的當(dāng)前線程副本中的值設(shè)置為指定值。大部分子類不需要重寫此方法,它們只依靠 initialValue() 方法來設(shè)置線程局部變量的值。

4)public void remove():移除此線程局部變量當(dāng)前線程的值。如果此線程局部變量隨后被當(dāng)前線程讀取,且這期間當(dāng)前線程沒有設(shè)置其值,則將調(diào)用其 initialValue() 方法重新初始化其值。這將導(dǎo)致在當(dāng)前線程多次調(diào)用 initialValue 方法。
關(guān)于Java多線程下 ThreadLocal 的應(yīng)用實例

下面是一個使用 ThreadLocal 的例子,每個線程產(chǎn)生自己獨立的序列號。就是使用ThreadLocal存儲每個線程獨立的序列號復(fù)本,線程之間互不干擾。


package sync; 
public class SequenceNumber { 
 // 定義匿名子類創(chuàng)建ThreadLocal的變量 
 private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>() { 
 // 覆蓋初始化方法 
 public Integer initialValue() { 
 return 0; 
 } 
 }; 
 // 下一個序列號 
 public int getNextNum() { 
 seqNum.set(seqNum.get() + 1); 
 return seqNum.get(); 
 } 
 private static class TestClient extends Thread { 
 private SequenceNumber sn; 
 public TestClient(SequenceNumber sn) { 
 this.sn = sn; 
 } 
 // 線程產(chǎn)生序列號 
 public void run() { 
 for (int i = 0; i < 3; i++) { 
 System.out.println("thread[" + Thread.currentThread().getName() + "] sn[" + sn.getNextNum() + "]"); 
 } 
 } 
 } 
 /** 
 * @param args 
 */ 
 public static void main(String[] args) { 
 SequenceNumber sn = new SequenceNumber(); 
 // 三個線程產(chǎn)生各自的序列號 
 TestClient t1 = new TestClient(sn); 
 TestClient t2 = new TestClient(sn); 
 TestClient t3 = new TestClient(sn); 
 t1.start(); 
 t2.start(); 
 t3.start(); 
 } 
}

程序的運行結(jié)果如下:

thread[Thread-1] sn[1] 
thread[Thread-1] sn[2] 
thread[Thread-1] sn[3] 
thread[Thread-2] sn[1] 
thread[Thread-2] sn[2] 
thread[Thread-2] sn[3] 
thread[Thread-0] sn[1]
thread[Thread-0] sn[2] 
thread[Thread-0] sn[3]

從運行結(jié)果可以看出,使用了 ThreadLocal 后,每個線程產(chǎn)生了獨立的序列號,沒有相互干擾。通常我們通過匿名內(nèi)部類的方式定義 ThreadLocal的子類,提供初始的變量值。

ThreadLocal和線程同步機制相比有什么優(yōu)勢呢?ThreadLocal和線程同步機制都是為了解決多線程中相同變量的訪問沖突問題。

在同步機制中,通過對象的鎖機制保證同一時間只有一個線程訪問變量。這時該變量是多個線程共享的,使用同步機制要求程序慎密地分析什么時候?qū)ψ兞窟M(jìn)行讀寫,什么時候需要鎖定某個對象,什么時候釋放對象鎖等繁雜的問題,程序設(shè)計和編寫難度相對較大。 而 ThreadLocal 則從另一個角度來解決多線程的并發(fā)訪問。ThreadLocal 會為每一個線程提供一個獨立的變量副本,從而隔離了多個線程對數(shù)據(jù)的訪問沖突。因為每一個線程都擁有自己的變量副本,從而也就沒有必要對該變量進(jìn)行同步了。ThreadLocal 提供了線程安全的共享對象,在編寫多線程代碼時,可以把不安全的變量封裝進(jìn) ThreadLocal。

概括起來說,對于多線程資源共享的問題,同步機制采用了“以時間換空間”的方式,而 ThreadLocal 采用了“以空間換時間”的方式。前者僅提供一份變量,讓不同的線程排隊訪問,而后者為每一個線程都提供了一份變量,因此可以同時訪問而互不影響。

需要注意的是 ThreadLocal 對象是一個本質(zhì)上存在風(fēng)險的工具,應(yīng)該在完全理解將要使用的線程模型之后,再去使用 ThreadLocal 對象。這就引出了線程池(thread pooling)的問題,線程池是一種線程重用技術(shù),有了線程池就不必為每個任務(wù)創(chuàng)建新的線程,一個線程可能會多次使用,用于這種環(huán)境的任何 ThreadLocal 對象包含的都是最后使用該線程的代碼所設(shè)置的狀態(tài),而不是在開始執(zhí)行新線程時所具有的未被初始化的狀態(tài)。 那么 ThreadLocal 是如何實現(xiàn)為每個線程保存獨立的變量的副本的呢?通過查看它的源代碼,我們會發(fā)現(xiàn),是通過把當(dāng)前“線程對象”當(dāng)作鍵,變量作為值存儲在一個 Map 中。

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; 
}

ThreadLocal不是用來解決對象共享訪問問題的,而主要是提供了保持對象的方法和避免參數(shù)傳遞的方便的對象訪問方式。歸納了兩點:

1、每個線程中都有一個自己的ThreadLocalMap類對象,可以將線程自己的對象保持到其中,各管各的,線程可以正確的訪問到自己的對象。

2、將一個共用的ThreadLocal靜態(tài)實例作為key,將不同對象的引用保存到不同線程的ThreadLocalMap中,然后在線程執(zhí)行的各處通過這個靜ThreadLocal實例的get()方法取得自己線程保存的那個對象,避免了將這個對象作為參數(shù)傳遞的麻煩。
關(guān)于Java多線程下 ThreadLocal 的應(yīng)用實例

Synchronized還是ThreadLocal?

ThreadLocal以空間換取時間,提供了一種非常簡便的多線程實現(xiàn)方式。因為多個線程并發(fā)訪問無需進(jìn)行等待,所以使用ThreadLocal 會獲得更大的性能。雖然使用ThreadLocal會帶來更多的內(nèi)存開銷,但這點開銷是微不足道的。因為保存在ThreadLocal中的對象,通常都是比較小的對象。另外使用ThreadLocal不能使用原子類型,只能使用Object類型。ThreadLocal的使用比synchronized要簡單得多。

ThreadLocal和Synchonized都用于解決多線程并發(fā)訪問。但是ThreadLocal與synchronized有本質(zhì)的區(qū)別。synchronized是利用鎖的機制,使變量或代碼塊在某一時該只能被一個線程訪問。而ThreadLocal為每一個線程都提供了變量的副本,使得每個線程在某一時間訪問到的并不是同一個對象,這樣就隔離了多個線程對數(shù)據(jù)的數(shù)據(jù)共享。而Synchronized卻正好相反,它用于在多個線程間通信時能夠獲得數(shù)據(jù)共享。

Synchronized用于線程間的數(shù)據(jù)共享,而ThreadLocal則用于線程間的數(shù)據(jù)隔離。

當(dāng)然ThreadLocal并不能替代synchronized,它們處理不同的問題域。Synchronized用于實現(xiàn)同步機制,比ThreadLocal更加復(fù)雜。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI