Java中ThreadLocal的設計與使用
最後更新:2017-02-28
來源:互聯網
上載者:User
設計 早在Java 1.2推出之時,Java平台中就引入了一個新的支援:java.lang.ThreadLocal,給我們在編寫多線程程式時提供了一種新的選擇。使用這個工具類可以很簡潔地編寫出優美的多線程程式,雖然ThreadLocal非常有用,但是似乎現在瞭解它、使用它的朋友還不多。
ThreadLocal是什麼
ThreadLocal是什麼呢?其實ThreadLocal並非是一個線程的本地實現版本,它並不是一個Thread,而是thread local variable(線程局部變數)。也許把它命名為ThreadLocalVar更加合適。線程局部變數(ThreadLocal)其實的功用非常簡單,就是為每一個使用該變數的線程都提供一個變數值的副本,是每一個線程都可以獨立地改變自己的副本,而不會和其它線程的副本衝突。從線程的角度看,就好像每一個線程都完全擁有該變數。線程局部變數並不是Java的新發明,在其它的一些語言編譯器實現(如IBM XL FORTRAN)中,它在語言的層次提供了直接的支援。因為Java中沒有提供在語言層次的直接支援,而是提供了一個ThreadLocal的類來提供支援,所以,在Java中編寫線程局部變數的代碼相對比較笨拙,這也許是線程局部變數沒有在Java中得到很好的普及的一個原因吧。
ThreadLocal的設計
首先看看ThreadLocal的介面:
Object get() ; // 返回當前線程的線程局部變數副本 protected Object initialValue(); // 返回該線程局部變數的當前線程的初始值
void set(Object value); // 設定當前線程的線程局部變數副本的值
ThreadLocal有3個方法,其中值得注意的是initialValue(),該方法是一個protected的方法,顯然是為了子類重寫而特意實現的。該方法返回當前線程在該線程局部變數的初始值,這個方法是一個延遲調用方法,在一個線程第1次調用get()或者set(Object)時才執行,並且僅執行1次。ThreadLocal中的確實實現直接返回一個null:
protected Object initialValue() { return null; }
ThreadLocal是如何做到為每一個線程維護變數的副本的呢?其實實現的思路很簡單,在ThreadLocal類中有一個Map,用於儲存每一個線程的變數的副本。比如下面的樣本實現:
public class ThreadLocal
{
private Map values = Collections.synchronizedMap(new HashMap());
public Object get()
{
Thread curThread = Thread.currentThread();
Object o = values.get(curThread);
if (o == null && !values.containsKey(curThread))
{
o = initialValue();
values.put(curThread, o);
}
return o;
}
public void set(Object newValue)
{
values.put(Thread.currentThread(), newValue);
}
public Object initialValue()
{
return null;
}
}
當然,這並不是一個工業強度的實現,但JDK中的ThreadLocal的實現總體思路也類似於此。
ThreadLocal的使用
如果希望線程局部變數初始化其它值,那麼需要自己實現ThreadLocal的子類並重寫該方法,通常使用一個內部匿名類對ThreadLocal進行子類化,比如下面的例子,SerialNum類為每一個類分配一個序號:
public class SerialNum
{
// The next serial number to be assigned
private static int nextSerialNum = 0;
private static ThreadLocal serialNum = new ThreadLocal()
{
protected synchronized Object initialValue()
{
return new Integer(nextSerialNum++);
}
};
public static int get()
{
return ((Integer) (serialNum.get())).intValue();
}
}
SerialNum類的使用將非常地簡單,因為get()方法是static的,所以在需要擷取當前線程的序號時,簡單地調用:
int serial = SerialNum.get();
即可。
線上程是活動的並且ThreadLocal對象是可訪問的時,該線程就持有一個到該線程局部變數副本的隱含引用,當該線程運行結束後,該線程擁有的所以線程局部變數的副本都將失效,並等待垃圾收集器收集。
ThreadLocal與其它同步機制的比較
ThreadLocal和其它同步機制相比有什麼優勢呢?ThreadLocal和其它所有的同步機制都是為瞭解決多線程中的對同一變數的存取違規,在普通的同步機制中,是通過對象加鎖來實現多個線程對同一變數的安全訪問的。這時該變數是多個線程共用的,使用這種同步機制需要很細緻地分析在什麼時候對變數進行讀寫,什麼時候需要鎖定某個對象,什麼時候釋放該對象的鎖等等很多。所有這些都是因為多個線程共用了資源造成的。ThreadLocal就從另一個角度來解決多線程的並發訪問,ThreadLocal會為每一個線程維護一個和該線程綁定的變數的副本,從而隔離了多個線程的資料,每一個線程都擁有自己的變數副本,從而也就沒有必要對該變數進行同步了。ThreadLocal提供了安全執行緒的共用對象,在編寫多線程代碼時,可以把不安全的整個變數封裝進ThreadLocal,或者把該對象的特定於線程的狀態封裝進ThreadLocal。
由於ThreadLocal中可以持有任何類型的對象,所以使用ThreadLocal get當前線程的值是需要進行強制類型轉換。但隨著新的Java版本(1.5)將模版的引入,新的支援模版參數的ThreadLocal<T>類將從中受益。也可以減少強制類型轉換,並將一些錯誤檢查提前到了編譯期,將一定程度地簡化ThreadLocal的使用。
總結
當然ThreadLocal並不能替代同步機制,兩者面向的問題領域不同。同步機制是為了同步多個線程對相同資源的並發訪問,是為了多個線程之間進行通訊的有效方式;而ThreadLocal是隔離多個線程的資料共用,從根本上就不在多個線程之間共用資源(變數),這樣當然不需要對多個線程進行同步了。所以,如果你需要進行多個線程之間進行通訊,則使用同步機制;如果需要隔離多個線程之間的共用衝突,可以使用ThreadLocal,這將極大地簡化你的程式,使程式更加易讀、簡潔。