ThreadLocal原始碼解析

追夢1819發表於2021-05-06
* This class provides thread-local variables.  These variables differ from
* their normal counterparts in that each thread that accesses one (via its
* {@code get} or {@code set} method) has its own, independently initialized
* copy of the variable.  {@code ThreadLocal} instances are typically private
* static fields in classes that wish to associate state with a thread (e.g.,
* a user ID or Transaction ID).複製程式碼

ThreadLocal類用來提供執行緒內部的區域性變數。這種變數在多執行緒環境下訪問(通過get或set方法訪問)時能保證各個執行緒裡的變數相對獨立於其他執行緒內的變數。ThreadLocal例項通常來說都是private static型別的,用於關聯執行緒和執行緒的上下文。

ThreadLocal的作用是提供執行緒內的區域性變數,這種變數線上程的生命週期內起作用,減少同一個執行緒內多個函式或者元件之間一些公共變數的傳遞的複雜度

ThreadLocal不是同步機制,也不解決共享物件的多執行緒競態條件問題。

ThreadLocal用來輔助平衡效率與執行緒的資源分配。

ThreadLocal會為每一個執行緒提供一個獨立的變數副本,從而隔離了多個執行緒對資料的併發訪問衝突。因為每一個執行緒都擁有自己的變數副本,從而也就沒有必要對該變數進行同步了。

對於多執行緒資源共享的問題,同步機制採用了“以時間換空間”的方式,ThreadLocal採用了“以空間換時間”的方式。同步機制僅提供一份變數,讓不同的執行緒排隊訪問,ThreadLocal為每一個執行緒都提供了一份變數, 因此可以同時訪問而互不影響。如果僅在單執行緒內訪問資料,就不需要同步,這種技術被稱為執行緒封閉(Thread Confinement)。Java語言及其核心庫提供了一些機制來幫助維持執行緒封閉性,例如ThreadLocal類。

ThreadLocal

ThreadLocal原始碼解析

set(T):儲存物件

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}複製程式碼

get():當某個執行緒初次呼叫ThreadLocal.get方法時,就會呼叫initialValue()來獲取初始值,否則返回由當前執行執行緒在呼叫set(T)的最新值,該方法避免了將這個物件作為引數傳遞的麻煩。

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();
}複製程式碼

(1)獲取當前執行緒

(2)根據當前執行緒獲取一個map 

(3)如果獲取的map不為空,則在map中以ThreadLocal的引用作為key來在map中獲取對應的value e,否則轉到(5)

(4)如果e不為null,則返回e.value,否則轉到(5)

(5)Map為空或者e為空,則通過initialValue函式獲取初始值value,然後用ThreadLocal的引用和value作為firstKey和firstValue建立一個新的Map

remove():將當前執行緒的ThreadLocal繫結的值刪除

public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        m.remove(this);
}複製程式碼

initialValue():用於設定 ThreadLocal 的初始值,預設返回 null,該函式是protected型別的,通常該函式都會以匿名內部類的形式被過載,以指定初始值

protected T initialValue() {
    return null;
}複製程式碼

getMap(Thread):獲取執行緒的ThreadLocalMap

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}複製程式碼

createMap(Thread,T):初始化執行緒的threadLocals,然後設定key-value

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}複製程式碼

createInheritedMap(ThreadLocalMap):

static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
    return new ThreadLocalMap(parentMap);
}複製程式碼

nextHashCode():

private static int nextHashCode() {
    return nextHashCode.getAndAdd(HASH_INCREMENT);
}複製程式碼

setInitialValue():

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;
}複製程式碼


JDK1.3:ThreadLocal<T>視為包含了Map<Thread,T>物件,其中儲存了特定於該執行緒的值。然後用執行緒的ID作為Map的key,例項物件作為Map的value,這樣就能達到各個執行緒的值隔離的效果

JDK1.8:每個Thread維護一個ThreadLocalMap對映表,這個對映表的key是ThreadLocal例項本身,value是真正需要儲存的Object。

JDK1.8優勢

  1. 每個Map的Entry數量變小了:之前是Thread的數量,現在是ThreadLocal的數量,能提高效能
  2. 當Thread銷燬之後對應的ThreadLocalMap也就隨之銷燬了,能減少記憶體使用量

ThreadLocal原始碼解析


每個執行緒中都有一個自己的ThreadLocalMap類物件,可以將執行緒自己的物件保持到其中,各管各的,執行緒可以正確的訪問到自己的物件。

java.lang.Thread:

ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;複製程式碼

ThreadLocal原始碼解析

static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}複製程式碼

ThreadLocalMap是使用ThreadLocal的弱引用作為Key的。儲存在Thread中,當執行緒終止後,這些值會作為垃圾回收

ThreadLocal原始碼解析

ThreadLocal不能繼承父執行緒的ThreadLocal的內容, InheritableThreadLocal類繼承於ThreadLocal類,InheritableThreadLocal變數值會自動傳遞給所有子執行緒,在父子執行緒之間傳遞資料。 建立一個執行緒時如果儲存了所有 InheritableThreadLocal 物件的值,那麼這些值也將自動傳遞給子執行緒。如果一個子執行緒呼叫 InheritableThreadLocal 的 get() ,那麼它將與它的父執行緒看到同一個物件。為保護執行緒安全性,您應該只對不可變物件(一旦建立, 其狀態就永遠不會被改變的物件)使用InheritableThreadLocal ,因為物件被多個執行緒共享。 InheritableThreadLocal合適用於把資料從父執行緒傳到子執行緒, 很例如使用者標識(user id)或事務標識(transaction id),但不能是有狀態物件,例如 JDBC Connection 。

private static ThreadLocal<Integer> integerThreadLocal = new ThreadLocal<>();
private static InheritableThreadLocal<Integer> inheritableThreadLocal =
        new InheritableThreadLocal<>();

@Test
public void inheritableThreadLocal() {
    // 父執行緒
    integerThreadLocal.set(1);
    inheritableThreadLocal.set(1);
    //結果:pool-1-thread-1:null/1
    threadFactory.newThread(() -> System.out.println(Thread.currentThread().getName() + ":"
            + integerThreadLocal.get() + "/"
            + inheritableThreadLocal.get())).start();
}複製程式碼

Reference(引用)

ThreadLocal原始碼解析


  • 強引用(FinalReference、Finalizer )

類似“Object obj =new Object()”這類的引用,只要強引用還存在,垃圾收集器永遠不會回收掉被引用的物件。強引用是Java的預設引用實現, 它會盡可能長時間的存活於JVM內, 當沒有任何物件指向它時, GC執行後也不會被回收。如果一個物件具有強引用,那就類似於必不可少的生活用品, 垃圾回收器絕不會回收它。當記憶體空間不足,Java虛擬機器寧願丟擲OutOfMemoryError錯誤, 使程式異常終止,也不會靠隨意回收具有強引用的物件來解決記憶體不足問題。JVM 系統採用 Finalizer 來管理每個強引用物件 , 並將其被標記要清理時加入 ReferenceQueue, 並逐一呼叫該物件的 finalize() 方法

Object obj = new Object();
Object strongReference = obj;
Assertions.assertSame(obj, strongReference);
obj = null;
System.gc();
Assertions.assertNull(obj);
Assertions.assertNotNull(strongReference);複製程式碼

ThreadLocal原始碼解析

Finalizer是FinalReference的子類,該類被final修飾,不可再被繼承,JVM實際操作的是Finalizer。當一個類滿足例項化FinalReference的條件時,JVM會呼叫Finalizer.register()進行註冊。

  • 軟引用(java.lang.ref.SoftReference)
用來描述一些還有用,但並非必需的物件。對於軟引用關聯著的物件,在系統將要發生記憶體溢位異常之前,將會把這些物件列進回收範圍之中並進行第二次回收。如果這次回收還是沒有足夠的記憶體,才會丟擲記憶體溢位異常。SoftReference 於 WeakReference 的特性基本一致,最大的區別在於 SoftReference 會盡可能長的保留引用直到 JVM 記憶體不足時才會被回收(虛擬機器保證),這一特性使得SoftReference非常適合快取應用
Object obj = new Object();
SoftReference<Object> softReference = new SoftReference<>(obj);
Assertions.assertNotNull(softReference.get());
obj = null;
System.gc();
Assertions.assertNull(obj);
// SoftReference 只有在 jvm OutOfMemory 之前才會被回收, 所以它非常適合快取應用
Assertions.assertNotNull(softReference.get());複製程式碼
  • 弱引用(java.lang.ref.WeakReference)

ThreadLocal原始碼解析

用來描述非必須物件的,但它的強度比軟引用要弱一些,被弱引用關聯的物件只能生存到下一次垃圾收集發生之前。當垃圾收集器工作時,無論當前記憶體是否足夠,都會回收掉只被弱引用關聯的物件。

Object obj = new Object();
WeakReference<Object> weakReference = new WeakReference<>(obj);
Assertions.assertSame(obj, weakReference.get());
obj = null;
System.gc();
Assertions.assertNull(weakReference.get());

Map<Object, Object> weakHashMap = new WeakHashMap<>();
Object key = new Object();
Object value = new Object();
weakHashMap.put(key, value);
Assertions.assertTrue(weakHashMap.containsValue(value));
key = null;
System.gc();
Thread.sleep(1000);
// 一旦沒有指向 key 的強引用, WeakHashMap 在 GC 後將自動刪除相關的 entry
Assertions.assertFalse(weakHashMap.containsValue(value));複製程式碼
  • 虛引用(java.lang.ref.PhantomReference)
稱為幽靈引用或者幻影引用,是最弱的一種引用關係。一個物件是否有虛引用的存在,完全不會對其生存時間構成影響,也無法通過虛引用來取得一個物件例項。為一個物件設定虛引用關聯的唯一目的就是希望能在這個物件被收集器回收時收到一個系統通知。


相關文章