ThreadLocal的核心思想很簡單:為每個獨立的線程提供一個變量的副本。
Java提供的synchronized關鍵字使用了“同步鎖”的機制來阻止線程的競爭訪問,即“以時間換空間”。: " 10pt; FONT-SIZE:> ThreadLocal則使用了“拷貝副本”的方式,人人有份,你用你的,我用我的,大家互不影響,是“以空間換時間”。每個線程修改變量時,實際上修改的是變量的副本,不怕影響到其它線程。
為了加深對ThreadLocal的理解,下面我使用一個例子來演示ThreadLocal如何隔離線程間的變量訪問和修改:
【1】SerialNum類
packageexample.thread.threadLocal;
publicclassSerialNum{
privatestaticintnextSerialNum=1;
@SuppressWarnings("unchecked")
privatestaticThreadLocalserialNum=newThreadLocal(){
protectedsynchronizedObjectinitialValue(){
returnnewInteger(nextSerialNum++);
}
};
publicstaticintget(){
return((Integer)(serialNum.get())).intValue();
}
@SuppressWarnings("unchecked")
publicstaticvoidset(IntegernewSerial){
serialNum.set(newSerial);
}
}
【2】GetSerialNumThread
packageexample.thread.threadLocal;
publicclassGetSerialNumThreadimplementsRunnable{
publicstaticvoidmain(Stringargs[]){
GetSerialNumThreadserialNumGetter=newGetSerialNumThread();
Threadt1=newThread(serialNumGetter,"ThreadA");
Threadt2=newThread(serialNumGetter,"ThreadB");
t1.start();
try{
t1.join();
}catch(InterruptedExceptione){
e.printStackTrace();
} t2.start();
}
publicvoidrun(){
intmySerialNum=getSerialNum();
System.out.println("線程"+Thread.currentThread().getName()
+"獲取到的序列號是"+mySerialNum);
System.out.println("線程"+Thread.currentThread().getName()
+"修改了序列號為"+(mySerialNum*3));
setSerialNum(mySerialNum*3);
System.out.println("線程"+Thread.currentThread().getName()
+"再次獲得的序列號是"+getSerialNum());
}
privateintgetSerialNum(){
returnSerialNum.get();
}
privatevoidsetSerialNum(intnewSerialNum){
SerialNum.set(newInteger(newSerialNum));
}
} 運行的結果如下:
線程 Thread A 獲取到的序列號是1
線程 Thread A 修改了序列號為3
線程 Thread A 再次獲得的序列號是3
線程 Thread B 獲取到的序列號是2
線程 Thread B 修改了序列號為6
線程 Thread B 再次獲得的序列號是6
可見第一個線程在調用SerialNum.set(int)方法修改static變量時,其實修改的是它自己的副本,而不是修改本地變量,第二個線程在初始化的時候拿到的序列號是2而不是7。
為什么會這樣呢?明明serialNum是靜態(tài)變量?。科鋵嵨覀冎恍枰纯碩hreadLocal的內部構造就知道了:
A. ThreadLocal的get()方法:
/**
*Returnsthevalueinthecurrentthread’scopyofthisthread-local
*variable. Createsandinitializesthecopyifthisisthefirsttime
*thethreadhascalledthismethod.
*
*@returnthecurrentthread’svalueofthisthread-local
*/
publicTget(){
Threadt=Thread.currentThread();
ThreadLocalMapmap=getMap(t);
if(map!=null)
return(T)map.get(this);
//Mapsareconstructedlazily. ifthemapforthisthread
//doesn’texist,createit,withthisThreadLocalandits
//initialvalueasitsonlyentry.
Tvalue=initialValue();
createMap(t,value);
returnvalue;
}
B. ThreadLocal的set()方法:
/**
*Setsthecurrentthread’scopyofthisthread-localvariable
*tothespecifiedvalue. Manyapplicationswillhavenoneedfor
*thisfunctionality,relyingsolelyonthe{@link#initialValue}
*methodtosetthevaluesofthread-locals.
*
*@paramvaluethevaluetobestoredinthecurrentthreads’copyof
* thisthread-local.
*/
publicvoidset(Tvalue){
Threadt=Thread.currentThread();
ThreadLocalMapmap=getMap(t);
if(map!=null)
map.set(this,value);
else
createMap(t,value);
}
可以看到ThreadLocal在內部維護了一個Map,將變量的值和線程綁定起來,get/set方法都是對該線程對應的value進行操作,所以不會影響到其它線程。
Java提供的synchronized關鍵字使用了“同步鎖”的機制來阻止線程的競爭訪問,即“以時間換空間”。: " 10pt; FONT-SIZE:> ThreadLocal則使用了“拷貝副本”的方式,人人有份,你用你的,我用我的,大家互不影響,是“以空間換時間”。每個線程修改變量時,實際上修改的是變量的副本,不怕影響到其它線程。
為了加深對ThreadLocal的理解,下面我使用一個例子來演示ThreadLocal如何隔離線程間的變量訪問和修改:
【1】SerialNum類
packageexample.thread.threadLocal;
publicclassSerialNum{
privatestaticintnextSerialNum=1;
@SuppressWarnings("unchecked")
privatestaticThreadLocalserialNum=newThreadLocal(){
protectedsynchronizedObjectinitialValue(){
returnnewInteger(nextSerialNum++);
}
};
publicstaticintget(){
return((Integer)(serialNum.get())).intValue();
}
@SuppressWarnings("unchecked")
publicstaticvoidset(IntegernewSerial){
serialNum.set(newSerial);
}
}
【2】GetSerialNumThread
packageexample.thread.threadLocal;
publicclassGetSerialNumThreadimplementsRunnable{
publicstaticvoidmain(Stringargs[]){
GetSerialNumThreadserialNumGetter=newGetSerialNumThread();
Threadt1=newThread(serialNumGetter,"ThreadA");
Threadt2=newThread(serialNumGetter,"ThreadB");
t1.start();
try{
t1.join();
}catch(InterruptedExceptione){
e.printStackTrace();
} t2.start();
}
publicvoidrun(){
intmySerialNum=getSerialNum();
System.out.println("線程"+Thread.currentThread().getName()
+"獲取到的序列號是"+mySerialNum);
System.out.println("線程"+Thread.currentThread().getName()
+"修改了序列號為"+(mySerialNum*3));
setSerialNum(mySerialNum*3);
System.out.println("線程"+Thread.currentThread().getName()
+"再次獲得的序列號是"+getSerialNum());
}
privateintgetSerialNum(){
returnSerialNum.get();
}
privatevoidsetSerialNum(intnewSerialNum){
SerialNum.set(newInteger(newSerialNum));
}
} 運行的結果如下:
線程 Thread A 獲取到的序列號是1
線程 Thread A 修改了序列號為3
線程 Thread A 再次獲得的序列號是3
線程 Thread B 獲取到的序列號是2
線程 Thread B 修改了序列號為6
線程 Thread B 再次獲得的序列號是6
可見第一個線程在調用SerialNum.set(int)方法修改static變量時,其實修改的是它自己的副本,而不是修改本地變量,第二個線程在初始化的時候拿到的序列號是2而不是7。
為什么會這樣呢?明明serialNum是靜態(tài)變量?。科鋵嵨覀冎恍枰纯碩hreadLocal的內部構造就知道了:
A. ThreadLocal的get()方法:
/**
*Returnsthevalueinthecurrentthread’scopyofthisthread-local
*variable. Createsandinitializesthecopyifthisisthefirsttime
*thethreadhascalledthismethod.
*
*@returnthecurrentthread’svalueofthisthread-local
*/
publicTget(){
Threadt=Thread.currentThread();
ThreadLocalMapmap=getMap(t);
if(map!=null)
return(T)map.get(this);
//Mapsareconstructedlazily. ifthemapforthisthread
//doesn’texist,createit,withthisThreadLocalandits
//initialvalueasitsonlyentry.
Tvalue=initialValue();
createMap(t,value);
returnvalue;
}
B. ThreadLocal的set()方法:
/**
*Setsthecurrentthread’scopyofthisthread-localvariable
*tothespecifiedvalue. Manyapplicationswillhavenoneedfor
*thisfunctionality,relyingsolelyonthe{@link#initialValue}
*methodtosetthevaluesofthread-locals.
*
*@paramvaluethevaluetobestoredinthecurrentthreads’copyof
* thisthread-local.
*/
publicvoidset(Tvalue){
Threadt=Thread.currentThread();
ThreadLocalMapmap=getMap(t);
if(map!=null)
map.set(this,value);
else
createMap(t,value);
}
可以看到ThreadLocal在內部維護了一個Map,將變量的值和線程綁定起來,get/set方法都是對該線程對應的value進行操作,所以不會影響到其它線程。

