說到ThreadLocal,首先說說這個類的命名。直觀上看好像是個Thread的什麼親戚,但其實它想表達的意思是執行緒區域變數,也就是說每 個線程自己的變數。它作為一個JDK5以後支援範型的類,主要是想利用範型把非安全執行緒的共用變數,封裝成綁定線程的安全不共用變數。 這樣的解釋我想我們多半能猜出它的實現思路:把一個共用變數在每個線程使用時,初始化一個副本,並且和線程綁定。以後所有的線程對 共用變數的操作都是對線程內部那個副本,完全的線程內部變數的操作。
要實現這樣功能類的設計,主要技術點是要能把副本和線程綁定映射,程式可以安全尋找到當前線程的副本,修改後安全的綁定給線程 。所以我們想到了Map的儲存結構,ThreadLocal內部就是使用了安全執行緒的Map形式的儲存把currentThread和變數副本一一映射。
既然要把共用的變成不共用的,那麼就要變數滿足一個情境:變數的狀態不需要共用。例如無狀態的bean在多線程之間是安全的,因為 線程之間不需要同步bean的狀態,用了就走(很不負責啊),想用就用。但是對於有狀態的bean線上程之間則必須小心,線程A剛看到狀態 是a,正想利用a做事情,線程B把bean的狀態改為了b,結果做了不該做的。但是如果有狀態的bean不需要共用狀態,每個線程看到狀態a或 者b都可以做出自己的行為,這種情況下不同步的選擇就是ThreadLocal了。
利用ThreadLocal的優勢就在於根本不用擔心有狀態的bean為了狀態的一致而犧牲效能,去使用synchronized限制只有一個線程在同一時 間做出關於bean狀態的行為。而是多個線程同時根據自己持有的bean的副本的狀態做出行為,這樣的轉變對於並發的支援是那麼的不可思議 。例如一個 Dao內有個Connection的屬性,當多個線程使用Dao的同一個執行個體時,問題就來了:多個線程用一個Connection,而且它還是有 串連,關閉等等的狀態轉變的,我們很敏感的想到這個屬性不安全!再看這個屬性,其實它是多麼的想告訴線程哥哥們:我的這些狀態根本 就不想共用,不要因為我的狀態而不敢一起追求。線程哥哥們也鬱悶:你要是有多胞胎姐妹該多好啊!這時候ThreadLocal大哥過來說:小 菜,我來搞定!你們這些線程一人一個 Connection,你想關就關,想串連就串連,再也不用抱怨說它把你的串連關了。這樣Dao的執行個體再也 不用因為自己有個不安全的屬性而自卑了。當然 ThreadLocal的思路雖然是很好的,但是官方的說法是最初的實現效能並不好,隨著Map結 構和Thread.currentThread的改進,效能較之synchronized才有了明顯的優勢。所以要是使用的是JDK1.2,JDK1.3等等,也不要妄想麻雀變 鳳凰...
再看ThreadLocal和synchronized的本質。前者不在乎多佔點空間,但是絕對的忍受不了等待;後者對等待無所謂,但是就是不喜歡浪費 空間。這也反映出了演算法的一個規律:通常是使用情境決定時間和空間的比例,既省時又省地的演算法多數情況下只存在於幻想之中。下面寫 個簡單的例子解釋一下,不過個人覺得設計的例子不太好,以後有實際的啟發再替換吧。
Java代碼
import java.util.concurrent.atomic.AtomicInteger;
/**
* User: yanxuxin
* Date: Dec 14, 2009
* Time: 9:26:41 PM
*/
public class ThreadLocalSample extends Thread {
private OperationSample2 operationSample;
public ThreadLocalSample(OperationSample2 operationSample) {
this.operationSample = operationSample;
}
@Override
public void run() {
operationSample.printAndIncrementNum();
}
public static void main(String[] args) {
final OperationSample2 operation = new OperationSample2();//The shared Object for threads.
for (int i = 0; i < 5; i++) {
new ThreadLocalSample(operation).start();
}
}
}
class OperationSample {
private int num;
//public synchronized void printAndIncrementNum() {
public void printAndIncrementNum() {
for (int i = 0; i < 2; i++) {
System.out.println(Thread.currentThread().getName() + "[id=" + num + "]");
num += 10;
}
}
}
class OperationSample2 {
private static ThreadLocal<Integer> threadArg = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 0;
}
};
public void printAndIncrementNum() {
for (int i = 0; i < 2; i++) {
int num = threadArg.get();
threadArg.set(num + 10);
System.out.println(Thread.currentThread().getName() + "[id=" + num + "]");
}
}
}
class OperationSample3 {
private static final AtomicInteger uniqueId = new AtomicInteger(0);
private static ThreadLocal<Integer> threadArg = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return uniqueId.getAndIncrement();
}
};
public void printAndIncrementNum() {
for (int i = 0; i < 2; i++) {
int num = threadArg.get();
threadArg.set(num + 10);
System.out.println(Thread.currentThread().getName() + "[id=" + num + "]");
}
}
}