ThreadLocal的設計思想
既然多線程通路同一個變量會造成線程安全的問題,那麼建立出來一個變量,需要使用這個變量的線程将該變量拷貝一份,并且拷貝到每一個線程的變量是線程私有的,使得變量線上程之間隔離起來使用,避免了線程之間的交錯使用資料造成的線程安全問題。
ThreadLocalMap
ThreadLocalMap是ThreadLocal的内部類,它中間有一個内部類 Entry。
// 将Entry的key設定為弱引用
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
// 構造方法,ThreadLocal作為鍵,value作為值
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
ThreadLocalMap将Entry設定為弱引用,是為了防止記憶體洩露。
将key設定為弱引用,在下一次GC的時候,将弱引用指向的對象回收。在多線程中,假設多個線程使用同一個ThreadLocal類型的變量,也就是每個線程的ThreadLocalMap的其中一個Entry中的key使用的是同一個ThreadLocal類型的變量。
舉例
三個線程中的每個線程的 ThreadLocalMap 的其中一個 Entry 中的 key 使用的是同一個 ThreadLocal 類型變量的位址。都指向了 ThreadLocal1 。
此時假設是強引用:多個線程依賴同一個 ThreadLocal1 ,此時 線程 1 的 ThreadLocal1 使用結束了想要釋放記憶體,但是由于是強引用(因為還有其他線程還在指向 ThreadLocal1),這就導緻了線程1 的持有 ThreadLocal1 的 Entry 占有的記憶體無法釋放,導緻了記憶體洩露,使用弱引用的時候,這種問題就可以解決。
ThreadLocal中還設計了Entry數組來存放多個Entry。
使用ThreadLocal
下面是一段示範代碼,main函數作為線程,對ThreadLocal進行操作。
package com.school.service;
public class Main {
public static void main(String[] args) {
ThreadLocal t = new ThreadLocal();
t.set("jqz");
t.set("zy");
String s = (String)t.get();
System.out.println(s);
}
}
set方法
點進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);
}
點進getMap(t)這個方法,代碼如下
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
getMap方法參數為線程對象,可以看見傳回值為ThreadLocal,點進去t.threadLocals;
可以看到
這是在Thread類裡的,Thread類中内置了ThreadLocalMap這個類型的參數,可以發現每個線程都是有自己獨立的ThreadLocalMap對象。
ThreadLocal通過getMap方法擷取線程對象的ThreadLocal。
繼續向下看set方法。
// set方法,參數為要插入的值
public void set(T value) {
// 擷取對目前執行線程對象的引用
Thread t = Thread.currentThread();
// 擷取線程中的ThreadLocalMap對象
ThreadLocalMap map = getMap(t);
// 如果map裡面已經插入過值了
if (map != null)
// 對map中再set進去目前傳入的值,它的鍵為這個ThreadLocal對象
map.set(this, value);
// 如果map裡面沒有插入過值
else
// 建立新的ThreadLocalMap對象,插入值進去,它的鍵為這個ThreadLocal對象
createMap(t, value);
}
get方法
點進get方法,進去為下方代碼,為其加上注釋。
// get方法,傳回目前線程,在該ThreadLocal中的值
public T get() {
// 擷取對目前執行線程對象的引用
Thread t = Thread.currentThread();
// 擷取線程中的ThreadLocalMap對象
ThreadLocalMap map = getMap(t);
// 如果ThreadLocalMap存在
if (map != null) {
// 通過ThreadLocal作為鍵,擷取ThreadLocalMap的Entry對象
ThreadLocalMap.Entry e = map.getEntry(this);
// 如果Entry不為空
if (e != null) {
// 擷取結果
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
private T setInitialValue() {
// 将value設為空值
T value = initialValue();
// 擷取目前線程對象
Thread t = Thread.currentThread();
// 擷取目前線程的ThreadLocalMap對象
ThreadLocalMap map = getMap(t);
// 将控制設定進ThreadLocalMap
if (map != null)
map.set(this, value);
else
createMap(t, value);
// 放回空值
return value;
}
場景
如果有兩個線程使用相同的ThreadLocal,通路值會相同嗎?
如果相同的線程通路兩個不同的ThreadLocal,通路值會相同嗎?
答
隻有相同對象通路相同的ThreadLocal,通路值才會相同。