ThreadLocal是什麼
ThreadLocal是一個關於創建線程局部變量的類。
通常情況下,我們創建的變量是可以被任何一個線程訪問並修改的。而使用ThreadLocal創建的變量只能被當前線程訪問,其他線程則無法訪問和修改。
Global && Local
上面的兩個修飾看似矛盾,實則不然。
- Global 意思是在當前線程中,任何一個點都可以訪問到ThreadLocal的值。
- Local 意思是該線程的ThreadLocal只能被該線程訪問,一般情況下其他線程訪問不到。
用法簡介
創建,支持泛型
1 |
|
set方法
1 |
|
get方法
1 |
|
完整的使用示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
ThreadLocal初始值
爲ThreadLocal設置默認的get初始值,需要重寫initialValue
方法,下面是一段代碼,我們將默認值修改成了線程的名字
1 2 3 4 5 6 |
|
Android中的應用
在Android中,Looper類就是利用了ThreadLocal的特性,保證每個線程只存在一個Looper對象。
1 2 3 4 5 6 7 |
|
如何實現
爲了更好的掌握ThreadLocal,我認爲了解其內部實現是很有必要的,這裏我們以set方法從起始看一看ThreadLocal的實現原理。
下面是ThreadLocal的set方法,大致意思爲
- 首先獲取當前線程
- 利用當前線程作爲句柄獲取一個ThreadLocalMap的對象
- 如果上述ThreadLocalMap對象不爲空,則設置值,否則創建這個ThreadLocalMap對象並設置值
源碼如下
1 2 3 4 5 6 7 8 |
|
下面是一個利用Thread對象作爲句柄獲取ThreadLocalMap對象的代碼
1 2 3 |
|
上面的代碼獲取的實際上是Thread對象的threadLocals變量,可參考下面代碼
1 2 3 4 5 6 |
|
而如果一開始設置,即ThreadLocalMap對象未創建,則新建ThreadLocalMap對象,並設置初始值。
1 2 3 |
|
總結:實際上ThreadLocal的值是放入了當前線程的一個ThreadLocalMap實例中,所以只能在本線程中訪問,其他線程無法訪問。
對象存放在哪裏
在Java中,棧內存歸屬於單個線程,每個線程都會有一個棧內存,其存儲的變量只能在其所屬線程中可見,即棧內存可以理解成線程的私有內存。而堆內存中的對象對所有線程可見。堆內存中的對象可以被所有線程訪問。
問:那麼是不是說ThreadLocal的實例以及其值存放在棧上呢?
其實不是,因爲ThreadLocal實例實際上也是被其創建的類持有(更頂端應該是被線程持有)。而ThreadLocal的值其實也是被線程實例持有。
它們都是位於堆上,只是通過一些技巧將可見性修改成了線程可見。
關於堆和棧的比較,請參考Java中的堆和棧的區別
真的只能被一個線程訪問麼
既然上面提到了ThreadLocal只對當前線程可見,是不是說ThreadLocal的值只能被一個線程訪問呢?
使用InheritableThreadLocal可以實現多個線程訪問ThreadLocal的值。
如下,我們在主線程中創建一個InheritableThreadLocal的實例,然後在子線程中得到這個InheritableThreadLocal實例設置的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
上面的代碼輸出的日誌信息爲
1 |
|
使用InheritableThreadLocal可以將某個線程的ThreadLocal值在其子線程創建時傳遞過去。因爲在線程創建過程中,有相關的處理邏輯。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
上面代碼就是在線程創建的時候,複製父線程的inheritableThreadLocals的數據。
會導致內存泄露麼
有網上討論說ThreadLocal會導致內存泄露,原因如下
- 首先ThreadLocal實例被線程的ThreadLocalMap實例持有,也可以看成被線程持有。
- 如果應用使用了線程池,那麼之前的線程實例處理完之後出於複用的目的依然存活
- 所以,ThreadLocal設定的值被持有,導致內存泄露。
上面的邏輯是清晰的,可是ThreadLocal並不會產生內存泄露,因爲ThreadLocalMap在選擇key的時候,並不是直接選擇ThreadLocal實例,而是ThreadLocal實例的弱引用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
所以實際上從ThreadLocal設計角度來說是不會導致內存泄露的。關於弱引用,瞭解更多,請訪問譯文:理解Java中的弱引用
使用場景
- 實現單個線程單例以及單個線程上下文信息存儲,比如交易id等
- 實現線程安全,非線程安全的對象使用ThreadLocal之後就會變得線程安全,因爲每個線程都會有一個對應的實例
- 承載一些線程相關的數據,避免在方法中來回傳遞參數
注意:Android的ThreadLocal與Java實現略有不同,但是原理是一致的。
參考文章
- Java ThreadLocal
- Threadlocals and memory leaks in J2EE
- Java Thread Local – How to use and code sample
- ThreadLocal in Java – Example Program and Tutorial
from:https://droidyue.com/blog/2016/03/13/learning-threadlocal-in-java/