您好,登錄后才能下訂單哦!
ThreadLocal很容易讓人望文生義,想當然地認為是一個“本地線程” 。其實,ThreadLocal并不是一個 Thread,而是 Thread 的局部變量,也許把它命名為 ThreadLocalVariable更容易讓人理解一些。當使用 ThreadLocal 維護變量時,ThreadLocal 為每個使用該變量的線程提供獨立的變量副本,所以每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應的副本。
首先,ThreadLocal 不是用來解決共享對象的多線程訪問問題的,一般情況下,通過ThreadLocal.set() 到線程中的對象是該線程自己使用的對象,其他線程是不需要訪問的,也訪問不到的。各個線程中訪問的是不同的對象。
另外,說ThreadLocal使得各線程能夠保持各自獨立的一個對象,并不是通過ThreadLocal.set()來實現的,而是通過每個線程中的new 對象 的操作來創建的對象,每個線程創建一個,不是什么對象的拷貝或副本。通過ThreadLocal.set()將這個新創建的對象的引用保存到各線程的自己的一個map中,每個線程都有這樣一個map,執行ThreadLocal.get()時,各線程從自己的map中取出放進去的對象,因此取出來的是各自自己線程中的對象,ThreadLocal實例是作為map的key來使用的。
如果ThreadLocal.set()進去的東西本來就是多個線程共享的同一個對象,那么多個線程的ThreadLocal.get()取得的還是這個共享對象本身,還是有并發訪問問題。
JDK 5 以后提供了泛型支持,ThreadLocal 被定義為支持泛型:
public class ThreadLocal<T> extends Object
T 為線程局部變量的類型。該類定義了 4 個方法:
1) protected T initialValue(): 返回此線程局部變量的當前線程的“初始值”。線程第一次使用 get() 方法訪問變量時將調用此方法,但如果線程之前調用了 set(T) 方法,則不會對該線程再調用 initialValue 方法。通常,此方法對每個線程最多調用一次,但如果在調用 get() 后又調用了 remove(),則可能再次調用此方法。 該實現返回 null;如果程序員希望線程局部變量具有 null 以外的值,則必須為 ThreadLocal 創建子類,并重寫此方法。通常將使用匿名內部類完成此操作。
2)public T get():返回此線程局部變量的當前線程副本中的值。如果變量沒有用于當前線程的值,則先將其初始化為調用 initialValue() 方法返回的值。
3)public void set(T value):將此線程局部變量的當前線程副本中的值設置為指定值。大部分子類不需要重寫此方法,它們只依靠 initialValue() 方法來設置線程局部變量的值。
4)public void remove():移除此線程局部變量當前線程的值。如果此線程局部變量隨后被當前線程讀取,且這期間當前線程沒有設置其值,則將調用其 initialValue() 方法重新初始化其值。這將導致在當前線程多次調用 initialValue 方法。
下面是一個使用 ThreadLocal 的例子,每個線程產生自己獨立的序列號。就是使用ThreadLocal存儲每個線程獨立的序列號復本,線程之間互不干擾。
package sync;
public class SequenceNumber {
// 定義匿名子類創建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;
}
// 線程產生序列號
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();
// 三個線程產生各自的序列號
TestClient t1 = new TestClient(sn);
TestClient t2 = new TestClient(sn);
TestClient t3 = new TestClient(sn);
t1.start();
t2.start();
t3.start();
}
}
程序的運行結果如下:
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]
從運行結果可以看出,使用了 ThreadLocal 后,每個線程產生了獨立的序列號,沒有相互干擾。通常我們通過匿名內部類的方式定義 ThreadLocal的子類,提供初始的變量值。
ThreadLocal和線程同步機制相比有什么優勢呢?ThreadLocal和線程同步機制都是為了解決多線程中相同變量的訪問沖突問題。
在同步機制中,通過對象的鎖機制保證同一時間只有一個線程訪問變量。這時該變量是多個線程共享的,使用同步機制要求程序慎密地分析什么時候對變量進行讀寫,什么時候需要鎖定某個對象,什么時候釋放對象鎖等繁雜的問題,程序設計和編寫難度相對較大。 而 ThreadLocal 則從另一個角度來解決多線程的并發訪問。ThreadLocal 會為每一個線程提供一個獨立的變量副本,從而隔離了多個線程對數據的訪問沖突。因為每一個線程都擁有自己的變量副本,從而也就沒有必要對該變量進行同步了。ThreadLocal 提供了線程安全的共享對象,在編寫多線程代碼時,可以把不安全的變量封裝進 ThreadLocal。
概括起來說,對于多線程資源共享的問題,同步機制采用了“以時間換空間”的方式,而 ThreadLocal 采用了“以空間換時間”的方式。前者僅提供一份變量,讓不同的線程排隊訪問,而后者為每一個線程都提供了一份變量,因此可以同時訪問而互不影響。
需要注意的是 ThreadLocal 對象是一個本質上存在風險的工具,應該在完全理解將要使用的線程模型之后,再去使用 ThreadLocal 對象。這就引出了線程池(thread pooling)的問題,線程池是一種線程重用技術,有了線程池就不必為每個任務創建新的線程,一個線程可能會多次使用,用于這種環境的任何 ThreadLocal 對象包含的都是最后使用該線程的代碼所設置的狀態,而不是在開始執行新線程時所具有的未被初始化的狀態。 那么 ThreadLocal 是如何實現為每個線程保存獨立的變量的副本的呢?通過查看它的源代碼,我們會發現,是通過把當前“線程對象”當作鍵,變量作為值存儲在一個 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不是用來解決對象共享訪問問題的,而主要是提供了保持對象的方法和避免參數傳遞的方便的對象訪問方式。歸納了兩點:
1、每個線程中都有一個自己的ThreadLocalMap類對象,可以將線程自己的對象保持到其中,各管各的,線程可以正確的訪問到自己的對象。
2、將一個共用的ThreadLocal靜態實例作為key,將不同對象的引用保存到不同線程的ThreadLocalMap中,然后在線程執行的各處通過這個靜ThreadLocal實例的get()方法取得自己線程保存的那個對象,避免了將這個對象作為參數傳遞的麻煩。
Synchronized還是ThreadLocal?
ThreadLocal以空間換取時間,提供了一種非常簡便的多線程實現方式。因為多個線程并發訪問無需進行等待,所以使用ThreadLocal 會獲得更大的性能。雖然使用ThreadLocal會帶來更多的內存開銷,但這點開銷是微不足道的。因為保存在ThreadLocal中的對象,通常都是比較小的對象。另外使用ThreadLocal不能使用原子類型,只能使用Object類型。ThreadLocal的使用比synchronized要簡單得多。
ThreadLocal和Synchonized都用于解決多線程并發訪問。但是ThreadLocal與synchronized有本質的區別。synchronized是利用鎖的機制,使變量或代碼塊在某一時該只能被一個線程訪問。而ThreadLocal為每一個線程都提供了變量的副本,使得每個線程在某一時間訪問到的并不是同一個對象,這樣就隔離了多個線程對數據的數據共享。而Synchronized卻正好相反,它用于在多個線程間通信時能夠獲得數據共享。
Synchronized用于線程間的數據共享,而ThreadLocal則用于線程間的數據隔離。
當然ThreadLocal并不能替代synchronized,它們處理不同的問題域。Synchronized用于實現同步機制,比ThreadLocal更加復雜。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。