先看JDK關於ThreadLocal的類註釋:web
This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. 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提供了線程內部的局部變量;每一個線程都有本身的,獨立的初始化變量副本;ThreadLocal實例一般是類中的private static字段,該類通常在線程狀態相關(或線程上下文)中使用。數據庫
Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).
翻譯過來大概的意思爲:只要線程處於活動狀態且ThreadLocal實例是可訪問的狀態下,每一個線程都持有對其線程局部變量副本的隱式引用;在線程消亡後,ThreadLocal實例s的全部副本都將進行垃圾回收(除非存在對這些副本的其餘引用)。數組
一、多線程下使用日誌追蹤,如Logback或Log4j的MDC組件
二、在事務中,connection綁定到當前線程來保證這個線程中的數據庫操做用的是同一個connection
三、dubbo的RpcContext的實現:<RpcContext是一個臨時狀態記錄器,當接收到RPC請求,或發起RPC請求時,RpcContext的狀態都會變化>
四、如今的分佈式trace系統中的traceId、spanId的傳遞等
五、web前臺的請求參數,在同一線程內多個方法之間隱式傳遞
。。。緩存
一個簡單的demo:多線程
public class TraceContext { private static final ThreadLocal<String> traceIdHolder = new ThreadLocal<String>() { @Override protected String initialValue() {//① return UUID.randomUUID().toString().replaceAll("-", ""); } }; public static void setTraceId(String traceId) { traceIdHolder.set(traceId);//② } public static String getTraceId() { return traceIdHolder.get();//③ } public static void removeTraceId() { traceIdHolder.remove();//④ } }
思考:ThreadLocal類型的traceIdHolder通常被修飾爲static、final、private,就是traceIdHolder在被使用的時候爲單例不可變(這不是常見的單例飽漢模式麼)。若是traceIdHolder定義爲多實例會怎麼樣?less
如下以JDK1.8實現解讀dom
ThreadLocal的構造函數爲空:public ThreadLocal() {}
分佈式
ThreadLocal的set方法:ide
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
獲取當前線程的ThreadLocalMap,有直接設置value、沒有新建
ThreadLocal的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(); }
從當前線程的ThreadLocalMap中查找Entry,若是沒必要爲null返回value,不然設置初值並返回setInitialValue()
ThreadLocal的remove()方法:
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
從當前線程的ThreadLocalMap中刪除
ThreadLocal的setInitialValue()方法:
private T setInitialValue() { T value = initialValue();//未覆蓋就是null Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; }
和set相似
查看Thread的threadLocals的字段定義:ThreadLocal.ThreadLocalMap threadLocals = null;
查看ThreadLocal的內部類ThreadLocalMap的定義:
雖然ThreadLocalMap命名含有'Map',但和Map接口沒任何關係。ThreadLocalMap底層是一個的散列表(可擴容的數組),並採用開放地址法來解決hash衝突。
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; } }
這裏先無論爲啥使用WeakReference定義。稍後討論
ThreadLocalMap.set方法:
private void set(ThreadLocal<?> key, Object value) { // We don't use a fast path as with get() because it is at // least as common to use set() to create new entries as // it is to replace existing ones, in which case, a fast // path would fail more often than not. Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); if (k == key) { e.value = value; return; } if (k == null) { replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }
每一個ThreadLocal對象都有一個hash值threadLocalHashCode,每初始化一個ThreadLocal對象,hash值就增長一個固定的大小0x61c88647。
定義以下:
private final int threadLocalHashCode = nextHashCode(); /** * The next hash code to be given out. Updated atomically. Starts at * zero. */ private static AtomicInteger nextHashCode = new AtomicInteger(); /** * The difference between successively generated hash codes - turns * implicit sequential thread-local IDs into near-optimally spread * multiplicative hash values for power-of-two-sized tables. */ private static final int HASH_INCREMENT = 0x61c88647; /** * Returns the next hash code. */ private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); }
ThreadLocalMap.set流程總結以下:
ThreadLocalMap.getEntry方法:
private Entry getEntry(ThreadLocal<?> key) { int i = key.threadLocalHashCode & (table.length - 1); Entry e = table[i]; if (e != null && e.get() == key) return e; else return getEntryAfterMiss(key, i, e); } private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) { Entry[] tab = table; int len = tab.length; while (e != null) { ThreadLocal<?> k = e.get(); if (k == key) return e; if (k == null) expungeStaleEntry(i); else i = nextIndex(i, len); e = tab[i]; } return null; } private int expungeStaleEntry(int staleSlot) { Entry[] tab = table; int len = tab.length; // expunge entry at staleSlot tab[staleSlot].value = null; tab[staleSlot] = null; size--; // Rehash until we encounter null Entry e; int i; for (i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) { ThreadLocal<?> k = e.get(); if (k == null) { e.value = null; tab[i] = null; size--; } else { int h = k.threadLocalHashCode & (len - 1); if (h != i) { tab[i] = null; // Unlike Knuth 6.4 Algorithm R, we must scan until // null because multiple entries could have been stale. while (tab[h] != null) h = nextIndex(h, len); tab[h] = e; } } } return i; }
ThreadLocalMap.getEntry流程總結以下:
ThreadLocalMap.remove方法:
private void remove(ThreadLocal<?> key) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { if (e.get() == key) { e.clear(); expungeStaleEntry(i); return; } } }
ThreadLocalMap.remove流程總結以下:
總結ThreadLocalMap:
Thread、ThreadLoal、ThreadLocalMap關係圖:
總體的對象關係圖
如下分析轉自知乎做者winwill2012,連接:我以爲是這樣的
如上圖,ThreadLocalMap使用ThreadLocal的弱引用做爲key,若是一個ThreadLocal沒有外部強引用引用他,那麼系統gc的時候,這個ThreadLocal勢必會被回收,這樣一來,ThreadLocalMap中就會出現key爲null的Entry,就沒有辦法訪問這些key爲null的Entry的value,若是當前線程再遲遲不結束的話,這些key爲null的Entry的value就會一直存在一條強引用鏈:
ThreadRef -> Thread -> ThreaLocalMap -> Entry -> value
永遠沒法回收,形成內存泄露。
所以在使用ThreadLocal的時候要手動調用remove方法,防止內存泄漏。
JDK建議將ThreadLocal變量定義成private static的,這樣的話ThreadLocal的生命週期就更長(類的靜態屬性引用的對象爲GCRoots),因爲一直存在ThreadLocal的強引用,因此ThreadLocal也就不會被回收,也就能保證任什麼時候候都能根據ThreadLocal的弱引用訪問到Entry的value值,而後remove它,防止內存泄露。
我覺的JDK建議將ThreadLocal變量定義成private static的還有個可能緣由是:單例,ThreadLocal對象是無狀態的,無含義的,聲明同一類型的ThreadLocal對象多實例,浪費ThreadLocalMap的存儲空間且對象更容易引發內存泄漏。