ThreadLocal,直譯爲「線程本地」或「本地線程」,若是你真的這麼認爲,那就錯了!其實,它就是一個容器,用於存放線程的局部變量,我認爲應該叫作 ThreadLocalVariable(線程局部變量)纔對,真不理解爲何當初 Sun 公司的工程師這樣命名。 java
早在 JDK 1.2 的時代,java.lang.ThreadLocal 就誕生了,它是爲了解決多線程併發問題而設計的,只不過設計得有些難用,因此至今沒有獲得普遍使用。其實它仍是挺有用的,不相信的話,咱們一塊兒來看看這個例子吧。 程序員
一個序列號生成器的程序,可能同時會有多個線程併發訪問它,要保證每一個線程獲得的序列號都是自增的,而不能相互干擾。 設計模式
先定義一個接口: 安全
public interface Sequence { int getNumber(); }
每次調用 getNumber() 方法可獲取一個序列號,下次再調用時,序列號會自增。 多線程
再作一個線程類: 併發
public class ClientThread extends Thread { private Sequence sequence; public ClientThread(Sequence sequence) { this.sequence = sequence; } @Override public void run() { for (int i = 0; i < 3; i++) { System.out.println(Thread.currentThread().getName() + " => " + sequence.getNumber()); } } }
在線程中連續輸出三次線程名與其對應的序列號。 ide
咱們先不用 ThreadLocal,來作一個實現類吧。 this
public class SequenceA implements Sequence { private static int number = 0; public int getNumber() { number = number + 1; return number; } public static void main(String[] args) { Sequence sequence = new SequenceA(); ClientThread thread1 = new ClientThread(sequence); ClientThread thread2 = new ClientThread(sequence); ClientThread thread3 = new ClientThread(sequence); thread1.start(); thread2.start(); thread3.start(); } }
序列號初始值是0,在 main() 方法中模擬了三個線程,運行後結果以下: spa
Thread-0 => 1
Thread-0 => 2
Thread-0 => 3
Thread-2 => 4
Thread-2 => 5
Thread-2 => 6
Thread-1 => 7
Thread-1 => 8
Thread-1 => 9 .net
因爲線程啓動順序是隨機的,因此並非0、一、2這樣的順序,這個好理解。爲何當 Thread-0 輸出了一、二、3以後,而 Thread-2 卻輸出了四、五、6呢?線程之間居然共享了 static 變量!這就是所謂的「非線程安全」問題了。
那麼如何來保證「線程安全」呢?對應於這個案例,就是說不一樣的線程可擁有本身的 static 變量,如何實現呢?下面看看另一個實現吧。
public class SequenceB implements Sequence { private static ThreadLocal<Integer> numberContainer = new ThreadLocal<Integer>() { @Override protected Integer initialValue() { return 0; } }; public int getNumber() { numberContainer.set(numberContainer.get() + 1); return numberContainer.get(); } public static void main(String[] args) { Sequence sequence = new SequenceB(); ClientThread thread1 = new ClientThread(sequence); ClientThread thread2 = new ClientThread(sequence); ClientThread thread3 = new ClientThread(sequence); thread1.start(); thread2.start(); thread3.start(); } }
經過 ThreadLocal 封裝了一個 Integer 類型的 numberContainer 靜態成員變量,而且初始值是0。再看 getNumber() 方法,首先從 numberContainer 中 get 出當前的值,加1,隨後 set 到 numberContainer 中,最後將 numberContainer 中 get 出當前的值並返回。
是否是很噁心?可是很強大!確實稍微饒了一下,咱們不妨把 ThreadLocal 當作是一個容器,這樣理解就簡單了。因此,這裏故意用 Container 這個單詞做爲後綴來命名 ThreadLocal 變量。
運行結果如何呢?看看吧。
Thread-0 => 1
Thread-0 => 2
Thread-0 => 3
Thread-2 => 1
Thread-2 => 2
Thread-2 => 3
Thread-1 => 1
Thread-1 => 2
Thread-1 => 3
每一個線程相互獨立了,一樣是 static 變量,對於不一樣的線程而言,它沒有被共享,而是每一個線程各一份,這樣也就保證了線程安全。 也就是說,TheadLocal 爲每個線程提供了一個獨立的副本!
搞清楚 ThreadLocal 的原理以後,有必要總結一下 ThreadLocal 的 API,其實很簡單。
爲何 initialValue() 方法是 protected 的呢?就是爲了提醒程序員們,這個方法是要大家來實現的,請給這個線程局部變量一個初始值吧。
瞭解了原理與這些 API,其實想一想 ThreadLocal 裏面不就是封裝了一個 Map 嗎?本身均可以寫一個 ThreadLocal 了,嘗試一下吧。
public class MyThreadLocal<T> { private Map<Thread, T> container = Collections.synchronizedMap(new HashMap<Thread, T>()); public void set(T value) { container.put(Thread.currentThread(), value); } public T get() { Thread thread = Thread.currentThread(); T value = container.get(thread); if (value == null && !container.containsKey(thread)) { value = initialValue(); container.put(thread, value); } return value; } public void remove() { container.remove(Thread.currentThread()); } protected T initialValue() { return null; } }以上徹底山寨了一個 ThreadLocal,其中中定義了一個同步 Map(爲何要這樣?請讀者自行思考),代碼應該很是容易讀懂。
下面用這 MyThreadLocal 再來實現一把看看。
public class SequenceC implements Sequence { private static MyThreadLocal<Integer> numberContainer = new MyThreadLocal<Integer>() { @Override protected Integer initialValue() { return 0; } }; public int getNumber() { numberContainer.set(numberContainer.get() + 1); return numberContainer.get(); } public static void main(String[] args) { Sequence sequence = new SequenceC(); ClientThread thread1 = new ClientThread(sequence); ClientThread thread2 = new ClientThread(sequence); ClientThread thread3 = new ClientThread(sequence); thread1.start(); thread2.start(); thread3.start(); } }
以上代碼其實就是將 ThreadLocal 替換成了 MyThreadLocal,僅此而已,運行效果和以前的同樣,也是正確的。
其實 ThreadLocal 能夠單獨成爲一種設計模式,就看你怎麼看了。
ThreadLocal 具體有哪些使用案例呢?
我想首先要說的就是:經過 ThreadLocal 存放 JDBC Connection,以達到事務控制的能力。
如何實現呢?下回分解!
注意:當您在一個類中使用了 static 成員變量的時候,必定要多問問本身,這個 static 成員變量須要考慮「線程安全」嗎?(也就是說,多個線程須要獨享本身的 static 成員變量嗎?)若是須要考慮,那就請用 ThreadLocal 吧!