ThreadLocal 那點事兒
ThreadLocal,直譯為“執行緒本地”或“本地執行緒”,如果你真的這麼認為,那就錯了!其實,它就是一個容器,用於存放執行緒的區域性變數,我認為應該叫做 ThreadLocalVariable(執行緒區域性變數)才對,真不理解為什麼當初 Sun 公司的工程師這樣命名。
早在 JDK 1.2 的時代,java.lang.ThreadLocal 就誕生了,它是為了解決多執行緒併發問題而設計的,只不過設計得有些難用,所以至今沒有得到廣泛使用。其實它還是挺有用的,不相信的話,我們一起來看看這個例子吧。
一個序列號生成器的程式,可能同時會有多個執行緒併發訪問它,要保證每個執行緒得到的序列號都是自增的,而不能相互干擾。
先定義一個介面:
1 2 3 4 |
|
每次呼叫 getNumber() 方法可獲取一個序列號,下次再呼叫時,序列號會自增。
再做一個執行緒類:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
private Sequence sequence;
for ( int i = 0 ; i < 3 ; i++) {
|
線上程中連續輸出三次執行緒名與其對應的序列號。
我們先不用 ThreadLocal,來做一個實現類吧。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
序列號初始值是0,在 main() 方法中模擬了三個執行緒,執行後結果如下:
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
由於執行緒啟動順序是隨機的,所以並不是0、1、2這樣的順序,這個好理解。為什麼當 Thread-0 輸出了1、2、3之後,而 Thread-2 卻輸出了4、5、6呢?執行緒之間竟然共享了 static 變數!這就是所謂的“非執行緒安全”問題了。
那麼如何來保證“執行緒安全”呢?對應於這個案例,就是說不同的執行緒可擁有自己的 static 變數,如何實現呢?下面看看另外一個實現吧。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
通過 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,其實很簡單。
- public void set(T value):將值放入執行緒區域性變數中
- public T get():從執行緒區域性變數中獲取值
- public void remove():從執行緒區域性變數中移除值(有助於 JVM 垃圾回收)
- protected T initialValue():返回執行緒區域性變數中的初始值(預設為 null)
為什麼 initialValue() 方法是 protected 的呢?就是為了提醒程式設計師們,這個方法是要你們來實現的,請給這個執行緒區域性變數一個初始值吧。
瞭解了原理與這些 API,其實想想 ThreadLocal 裡面不就是封裝了一個 Map 嗎?自己都可以寫一個 ThreadLocal 了,嘗試一下吧。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
以上完全山寨了一個 ThreadLocal,其中中定義了一個同步 Map(為什麼要這樣?請讀者自行思考),程式碼應該非常容易讀懂。
下面用這 MyThreadLocal 再來實現一把看看。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
以上程式碼其實就是將 ThreadLocal 替換成了 MyThreadLocal,僅此而已,執行效果和之前的一樣,也是正確的。
其實 ThreadLocal 可以單獨成為一種設計模式,就看你怎麼看了。
ThreadLocal 具體有哪些使用案例呢?
我想首先要說的就是:通過 ThreadLocal 存放 JDBC Connection,以達到事務控制的能力。
如何實現呢?下回分解!
注意:當您在一個類中使用了 static 成員變數的時候,一定要多問問自己,這個 static 成員變數需要考慮“執行緒安全”嗎?(也就是說,多個執行緒需要獨享自己的 static 成員變數嗎?)如果需要考慮,那就請用 ThreadLocal 吧!