ThreadLocal很容易讓人望文生義,想當(dāng)然地認(rèn)為是一個(gè)“本地線程” 。其實(shí),ThreadLocal并不是一個(gè) Thread,而是 Thread 的局部變量,也許把它命名為 ThreadLocalVariable更容易讓人理解一些。當(dāng)使用 ThreadLocal 維護(hù)變量時(shí),ThreadLocal 為每個(gè)使用該變量的線程提供獨(dú)立的變量副本,所以每一個(gè)線程都可以獨(dú)立地改變自己的副本,而不會(huì)影響其它線程所對(duì)應(yīng)的副本。
晉城ssl適用于網(wǎng)站、小程序/APP、API接口等需要進(jìn)行數(shù)據(jù)傳輸應(yīng)用場(chǎng)景,ssl證書(shū)未來(lái)市場(chǎng)廣闊!成為創(chuàng)新互聯(lián)的ssl證書(shū)銷售渠道,可以享受市場(chǎng)價(jià)格4-6折優(yōu)惠!如果有意向歡迎電話聯(lián)系或者加微信:18982081108(備注:SSL證書(shū)合作)期待與您的合作!
首先,ThreadLocal 不是用來(lái)解決共享對(duì)象的多線程訪問(wèn)問(wèn)題的,一般情況下,通過(guò)ThreadLocal.set() 到線程中的對(duì)象是該線程自己使用的對(duì)象,其他線程是不需要訪問(wèn)的,也訪問(wèn)不到的。各個(gè)線程中訪問(wèn)的是不同的對(duì)象。
另外,說(shuō)ThreadLocal使得各線程能夠保持各自獨(dú)立的一個(gè)對(duì)象,并不是通過(guò)ThreadLocal.set()來(lái)實(shí)現(xiàn)的,而是通過(guò)每個(gè)線程中的new 對(duì)象 的操作來(lái)創(chuàng)建的對(duì)象,每個(gè)線程創(chuàng)建一個(gè),不是什么對(duì)象的拷貝或副本。通過(guò)ThreadLocal.set()將這個(gè)新創(chuàng)建的對(duì)象的引用保存到各線程的自己的一個(gè)map中,每個(gè)線程都有這樣一個(gè)map,執(zhí)行ThreadLocal.get()時(shí),各線程從自己的map中取出放進(jìn)去的對(duì)象,因此取出來(lái)的是各自自己線程中的對(duì)象,ThreadLocal實(shí)例是作為map的key來(lái)使用的。
如果ThreadLocal.set()進(jìn)去的東西本來(lái)就是多個(gè)線程共享的同一個(gè)對(duì)象,那么多個(gè)線程的ThreadLocal.get()取得的還是這個(gè)共享對(duì)象本身,還是有并發(fā)訪問(wèn)問(wèn)題。
JDK 5 以后提供了泛型支持,ThreadLocal 被定義為支持泛型:
public class ThreadLocal
T 為線程局部變量的類型。該類定義了 4 個(gè)方法:
1) protected T initialValue(): 返回此線程局部變量的當(dāng)前線程的“初始值”。線程第一次使用 get() 方法訪問(wèn)變量時(shí)將調(diào)用此方法,但如果線程之前調(diào)用了 set(T) 方法,則不會(huì)對(duì)該線程再調(diào)用 initialValue 方法。通常,此方法對(duì)每個(gè)線程最多調(diào)用一次,但如果在調(diào)用 get() 后又調(diào)用了 remove(),則可能再次調(diào)用此方法。 該實(shí)現(xiàn)返回 null;如果程序員希望線程局部變量具有 null 以外的值,則必須為 ThreadLocal 創(chuàng)建子類,并重寫(xiě)此方法。通常將使用匿名內(nèi)部類完成此操作。
2)public T get():返回此線程局部變量的當(dāng)前線程副本中的值。如果變量沒(méi)有用于當(dāng)前線程的值,則先將其初始化為調(diào)用 initialValue() 方法返回的值。
3)public void set(T value):將此線程局部變量的當(dāng)前線程副本中的值設(shè)置為指定值。大部分子類不需要重寫(xiě)此方法,它們只依靠 initialValue() 方法來(lái)設(shè)置線程局部變量的值。
4)public void remove():移除此線程局部變量當(dāng)前線程的值。如果此線程局部變量隨后被當(dāng)前線程讀取,且這期間當(dāng)前線程沒(méi)有設(shè)置其值,則將調(diào)用其 initialValue() 方法重新初始化其值。這將導(dǎo)致在當(dāng)前線程多次調(diào)用 initialValue 方法。
下面是一個(gè)使用 ThreadLocal 的例子,每個(gè)線程產(chǎn)生自己獨(dú)立的序列號(hào)。就是使用ThreadLocal存儲(chǔ)每個(gè)線程獨(dú)立的序列號(hào)復(fù)本,線程之間互不干擾。
package sync;
public class SequenceNumber {
// 定義匿名子類創(chuàng)建ThreadLocal的變量
private static ThreadLocal seqNum = new ThreadLocal() {
// 覆蓋初始化方法
public Integer initialValue() {
return 0;
}
};
// 下一個(gè)序列號(hào)
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)生序列號(hào)
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();
// 三個(gè)線程產(chǎn)生各自的序列號(hào)
TestClient t1 = new TestClient(sn);
TestClient t2 = new TestClient(sn);
TestClient t3 = new TestClient(sn);
t1.start();
t2.start();
t3.start();
}
}
程序的運(yùn)行結(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]
從運(yùn)行結(jié)果可以看出,使用了 ThreadLocal 后,每個(gè)線程產(chǎn)生了獨(dú)立的序列號(hào),沒(méi)有相互干擾。通常我們通過(guò)匿名內(nèi)部類的方式定義 ThreadLocal的子類,提供初始的變量值。
ThreadLocal和線程同步機(jī)制相比有什么優(yōu)勢(shì)呢?ThreadLocal和線程同步機(jī)制都是為了解決多線程中相同變量的訪問(wèn)沖突問(wèn)題。
在同步機(jī)制中,通過(guò)對(duì)象的鎖機(jī)制保證同一時(shí)間只有一個(gè)線程訪問(wèn)變量。這時(shí)該變量是多個(gè)線程共享的,使用同步機(jī)制要求程序慎密地分析什么時(shí)候?qū)ψ兞窟M(jìn)行讀寫(xiě),什么時(shí)候需要鎖定某個(gè)對(duì)象,什么時(shí)候釋放對(duì)象鎖等繁雜的問(wèn)題,程序設(shè)計(jì)和編寫(xiě)難度相對(duì)較大。 而 ThreadLocal 則從另一個(gè)角度來(lái)解決多線程的并發(fā)訪問(wèn)。ThreadLocal 會(huì)為每一個(gè)線程提供一個(gè)獨(dú)立的變量副本,從而隔離了多個(gè)線程對(duì)數(shù)據(jù)的訪問(wèn)沖突。因?yàn)槊恳粋€(gè)線程都擁有自己的變量副本,從而也就沒(méi)有必要對(duì)該變量進(jìn)行同步了。ThreadLocal 提供了線程安全的共享對(duì)象,在編寫(xiě)多線程代碼時(shí),可以把不安全的變量封裝進(jìn) ThreadLocal。
概括起來(lái)說(shuō),對(duì)于多線程資源共享的問(wèn)題,同步機(jī)制采用了“以時(shí)間換空間”的方式,而 ThreadLocal 采用了“以空間換時(shí)間”的方式。前者僅提供一份變量,讓不同的線程排隊(duì)訪問(wèn),而后者為每一個(gè)線程都提供了一份變量,因此可以同時(shí)訪問(wèn)而互不影響。
需要注意的是 ThreadLocal 對(duì)象是一個(gè)本質(zhì)上存在風(fēng)險(xiǎn)的工具,應(yīng)該在完全理解將要使用的線程模型之后,再去使用 ThreadLocal 對(duì)象。這就引出了線程池(thread pooling)的問(wèn)題,線程池是一種線程重用技術(shù),有了線程池就不必為每個(gè)任務(wù)創(chuàng)建新的線程,一個(gè)線程可能會(huì)多次使用,用于這種環(huán)境的任何 ThreadLocal 對(duì)象包含的都是最后使用該線程的代碼所設(shè)置的狀態(tài),而不是在開(kāi)始執(zhí)行新線程時(shí)所具有的未被初始化的狀態(tài)。 那么 ThreadLocal 是如何實(shí)現(xiàn)為每個(gè)線程保存獨(dú)立的變量的副本的呢?通過(guò)查看它的源代碼,我們會(huì)發(fā)現(xiàn),是通過(guò)把當(dāng)前“線程對(duì)象”當(dāng)作鍵,變量作為值存儲(chǔ)在一個(gè) 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不是用來(lái)解決對(duì)象共享訪問(wèn)問(wèn)題的,而主要是提供了保持對(duì)象的方法和避免參數(shù)傳遞的方便的對(duì)象訪問(wèn)方式。歸納了兩點(diǎn):
1、每個(gè)線程中都有一個(gè)自己的ThreadLocalMap類對(duì)象,可以將線程自己的對(duì)象保持到其中,各管各的,線程可以正確的訪問(wèn)到自己的對(duì)象。
2、將一個(gè)共用的ThreadLocal靜態(tài)實(shí)例作為key,將不同對(duì)象的引用保存到不同線程的ThreadLocalMap中,然后在線程執(zhí)行的各處通過(guò)這個(gè)靜ThreadLocal實(shí)例的get()方法取得自己線程保存的那個(gè)對(duì)象,避免了將這個(gè)對(duì)象作為參數(shù)傳遞的麻煩。
Synchronized還是ThreadLocal?
ThreadLocal以空間換取時(shí)間,提供了一種非常簡(jiǎn)便的多線程實(shí)現(xiàn)方式。因?yàn)槎鄠€(gè)線程并發(fā)訪問(wèn)無(wú)需進(jìn)行等待,所以使用ThreadLocal 會(huì)獲得更大的性能。雖然使用ThreadLocal會(huì)帶來(lái)更多的內(nèi)存開(kāi)銷,但這點(diǎn)開(kāi)銷是微不足道的。因?yàn)楸4嬖赥hreadLocal中的對(duì)象,通常都是比較小的對(duì)象。另外使用ThreadLocal不能使用原子類型,只能使用Object類型。ThreadLocal的使用比synchronized要簡(jiǎn)單得多。
ThreadLocal和Synchonized都用于解決多線程并發(fā)訪問(wèn)。但是ThreadLocal與synchronized有本質(zhì)的區(qū)別。synchronized是利用鎖的機(jī)制,使變量或代碼塊在某一時(shí)該只能被一個(gè)線程訪問(wèn)。而ThreadLocal為每一個(gè)線程都提供了變量的副本,使得每個(gè)線程在某一時(shí)間訪問(wèn)到的并不是同一個(gè)對(duì)象,這樣就隔離了多個(gè)線程對(duì)數(shù)據(jù)的數(shù)據(jù)共享。而Synchronized卻正好相反,它用于在多個(gè)線程間通信時(shí)能夠獲得數(shù)據(jù)共享。
Synchronized用于線程間的數(shù)據(jù)共享,而ThreadLocal則用于線程間的數(shù)據(jù)隔離。
當(dāng)然ThreadLocal并不能替代synchronized,它們處理不同的問(wèn)題域。Synchronized用于實(shí)現(xiàn)同步機(jī)制,比ThreadLocal更加復(fù)雜。