ThreadLocal
是一個線程內部數據存儲類,經過他能夠在指定的線程中存儲數據。存儲後,只能在指定的線程中獲取到存儲的數據,對其餘線程來講沒法獲取到數據。java
平常使用場景很少,當某些數據是以線程爲做用域而且不一樣線程具備不一樣的數據副本的時候,能夠考慮使用ThreadLocal
。 Android
源碼的Lopper
、ActivityThread
以及AMS
中都用到了ThreadLocal
。ide
public class ThreadLocalActivity extends AppCompatActivity {
private ThreadLocal<String> name = new ThreadLocal<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_thread_local);
name.set("小明");
Log.d("ThreadLocalActivity", "Thread:" + Thread.currentThread().getName() + " name:" + name.get());
new Thread("thread1") {
@Override
public void run() {
name.set("小紅");
Log.d("ThreadLocalActivity", "Thread:" + Thread.currentThread().getName() + " name:" + name.get());
}
}.start();
new Thread("thread2") {
@Override
public void run() {
Log.d("ThreadLocalActivity", "Thread:" + Thread.currentThread().getName() + " name:" + name.get());
}
}.start();
}
}
複製代碼
運行結果:this
D/ThreadLocalActivity: Thread:main name:小明
D/ThreadLocalActivity: Thread:thread1 name:小紅
D/ThreadLocalActivity: Thread:thread2 name:null
複製代碼
能夠看到雖然訪問的是同一個ThreadLocal
對象,可是獲取到的值倒是不同的。spa
那麼爲何會形成這樣的結果呢?這就須要去看看ThreadLocal
的源碼實現,這裏的源碼版本爲API28
。主要看它的get
和set
方法。
set
方法:線程
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
複製代碼
set
方法中首先獲取了當前線程對象,而後經過getMap
方法傳入當前線程t
獲取到一個ThreadLocalMap
,接下來判斷這個map
是否爲空,不爲空就直接將當前ThreadLocal
做爲key
,set
方法中傳入要保存的值最爲value
,存放到map
中;若是map
爲空就調用createMap
方法建立一個map
並一樣將當前ThreadLocal
和要保存的值做爲key
和value
加入到map
中。
接下先看getMap
方法:設計
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
複製代碼
getMap
方法比較簡單,就是返回從傳入的當前線程對象的成員變量threadLocals
。 接着是createMap
方法:code
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
複製代碼
createMap
方法也很簡單就是new
了一個ThreadLocalMap
並賦給當前線程對象t
中的threadLocals
。 原來這個Map
是存放在Thread
類中的。因而進入Thread
類中查看。
Thread.java
第188-190行:對象
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
複製代碼
根據這裏的註釋能夠得知,每一個線程Thread
中都有一個ThreadLocalMap
類型的threadLocals
成員變量來保存數據,經過ThreadLocal
類來進行維護。這樣看來咱們每次在不一樣線程調用ThreadLocal
的set
方法set
的數據是存在不一樣線程的ThreadLocalMap
中的,就像註釋說的ThreadLocal
只是起了個維護ThreadLocalMap
的功能。想到是get
方法一樣也是到不一樣線程的ThreadLocalMap
去取數據。
get
方法:生命週期
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
複製代碼
果真,get
方法中一樣是先獲取當前線程對象,而後在拿着這個對象t
去獲取到t
中的ThreadLocalMap
,只要map
不等於null
就調用map.getEntry(this)
方法來獲取數據,由於ThreadLocalMap
裏使用一個內部類Entry
來存儲數據的,因此調用getEntry(this)
方法,傳入的key
是當前的ThreadLocal
。這樣獲取到Entry
類型數據e
,只要e
不爲null
,返回e.value
即先前存儲的數據。若是獲取到的map
爲null
又或者根據key
獲取Entry
爲null
,就調用setInitialValue
方法初始化一個value
返回。
setInitialValue
和initialValue
方法:內存
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
protected T initialValue() {
return null;
}
複製代碼
setInitialValue
方法中首先調用initialValue
方法初始化了一個空value
,以後的操做和set
方法相同,將這個空的value
加入到當前線程的ThreadLocalMap
中去,ThreadLocalMap
爲空就建立個Map
,最後返回這個空值。
至此,ThreadLocal
的get
、set
方法就都看過了,也理解了ThreadLocal
能夠在多個線程中操做而互不干擾的緣由。可是ThreadLocal
還有一個要注意的地方就是ThreadLocal
使用不當會形成內存泄漏。
內存泄漏的根本緣由是當一個對象已經不須要再使用本該被回收時,另一個正在使用的對象持有它的引用從而致使它不能被回收,致使本該被回收的對象不能被回收而停留在堆內存中。那麼ThreadLocal
中是在哪裏發生的呢?這就要看到ThreadLocalMap
中存儲數據的內部類Entry
。
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
複製代碼
能夠看到這個Entry
類,這裏的key
是使用了個弱引用,因此由於使用弱引用這裏的key
,ThreadLocal
會在JVM
下次GC
回收時候被回收,而形成了個key
爲null
的狀況,而外部ThreadLocalMap
是沒辦法經過null
key
來找到對應value
的。若是當前線程一直在運行,那麼線程中的ThreadLocalMap
也就一直存在,而map
中卻存在key
已經被回收爲null
對應的Entry
和value
卻一直存在不會被回收,形成內存的泄漏。
不過,這一點設計者也考慮到了,在get()
、set()
、remove()
方法調用的時候會清除掉線程ThreadLocalMap
中全部Entry
中Key
爲null
的Value
,並將整個Entry
設置爲null
,這樣在下次回收時就能將Entry
和value
回收。
這樣看上去好像是由於key
使用了弱引用才致使的內存泄漏,爲了解決還特地添加了清除null key
的功能,那麼是否是不用弱引用就能夠了呢?
很顯然不是這樣的。設計者使用弱引用是由緣由的。
ThreadLocal
對象已經被回收了可是ThreadLocalMap
還持有ThreadLocal
的強引用,如果沒有手動刪除,ThreadLocal
不會被回收,一樣致使內存泄漏。ThreadLocal
的對象被回收了,由於ThreadLocalMap
持有的是ThreadLocal
的弱引用,即便沒有手動刪除,ThreadLocal
也會被回收。nullkey
的value
在下一次ThreadLocalMap
調用set
、get
、remove
的時候會被清除。因此,因爲ThreadLocalMap
和線程Thread
的生命週期同樣長,若是沒有手動刪除Map
的中的key
,不管使用強引用仍是弱引用實際上都會出現內存泄漏,可是使用弱引用能夠多一層保護,null key
在下一次ThreadLocalMap
調用set
、get
、remove
的時候就會被清除。 所以,ThreadLocal
的內存內泄漏的真正緣由並不能說是由於ThreadLocalMap的key
使用了弱引用,而是由於ThreadLocalMap
和線程Thread
的生命週期同樣長,沒有手動刪除Map
的中的key
纔會致使內存泄漏。因此解決ThreadLocal
的內存泄漏問題就要每次使用完ThreadLocal
,都要記得調用它的remove()
方法來清除。