ThreadLocal是什么?
ThreadLocal是Java中的一個類,用于創建線程局部變量和解決線程安全。每個線程都有自己獨立的變量副本,彼此之間互不影響。它的主要作用是在多線程環境下,確保每個線程都有自己的變量實例,避免了變量共享帶來的線程安全問題。
ThreadLocal 的主要功能
- 線程局部變量:每個線程都有自己的變量副本,互不干擾。
- 線程安全:避免了多線程環境下的競爭和沖突。
ThreadLocal 的核心方法
Thread.currentThread()
獲取當前線程。getMap(t)
獲取當前線程的ThreadLocalMap
。map.getEntry(this)
從ThreadLocalMap
中獲取以當前ThreadLocal
為鍵的條目(Entry
)。- 如果條目存在,返回其值;否則調用
setInitialValue()
進行初始化。
set(T value)
set(T value)
方法用于設置當前線程的局部變量值,具體實現如下:
public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);
}
Thread.currentThread()
獲取當前線程。getMap(t)
獲取當前線程的ThreadLocalMap
。- 如果
ThreadLocalMap
存在,調用map.set(this, value)
設置值;否則調用createMap(t, value)
創建一個新的ThreadLocalMap
。
remove()
remove()
方法用于移除當前線程的局部變量值,具體實現如下:
public void remove() {ThreadLocalMap m = getMap(Thread.currentThread());if (m != null)m.remove(this);
}
- 獲取當前線程的
ThreadLocalMap
。 - 如果
ThreadLocalMap
存在,調用m.remove(this)
移除條目。
ThreadLocal 的典型使用場景
- 用戶會話管理:在web應用中,存儲與當前線程(用戶請求)相關的信息,如用戶會話、請求上下文等。
- 數據庫連接:為每個線程分配獨立的數據庫連接,避免連接共享帶來的線程安全問題。
- 事務管理:存儲與當前線程相關的事務信息,如事務狀態、事務ID等。
- 格式化工具:存儲與當前線程相關的工具實例,如SimpleDateFormat,避免工具共享帶來的線程安全問題。
ThreadLocal 的實現原理
ThreadLocal
是 Java 中用于實現線程局部變量的類,它為每個線程提供一個獨立的變量副本。實現這一點的關鍵在于每個線程都有一個 ThreadLocalMap
對象,ThreadLocalMap
類似于一個哈希表,存儲了當前線程所對應的所有 ThreadLocal
變量及其值。理解 ThreadLocal
的實現原理,需要深入探討其核心機制及內部結構。以下是詳細的解釋:
1. ThreadLocal
類的基本結構
ThreadLocal
類本身非常簡單,主要包含以下幾個重要的方法:
get()
: 獲取當前線程的局部變量值。set(T value)
: 設置當前線程的局部變量值。remove()
: 移除當前線程的局部變量值。
這些方法都依賴于每個線程獨有的 ThreadLocalMap
。
2. ThreadLocalMap
ThreadLocalMap
是 ThreadLocal
的一個靜態內部類,它是一個自定義的哈希表,用于存儲線程局部變量。每個線程都有一個 ThreadLocalMap
實例,存儲在線程對象中。其實現機制如下:
- 存儲位置:
ThreadLocalMap
存儲在每個線程的Thread
對象中,具體來說,ThreadLocalMap是Thread
類的一個實例變量:
public class Thread {// 部分源碼省略.../* ThreadLocal values pertaining to this thread. This map is maintained* by the ThreadLocal class. */ThreadLocal.ThreadLocalMap threadLocals = null;// 部分源碼省略...
}
- 鍵和值:
ThreadLocalMap
的鍵是ThreadLocal
對象,值是實際存儲的數據,類型為Object。為了避免ThreadLocal
對象的內存泄漏,ThreadLocalMap的鍵使用的是弱引用(WeakReference<ThreadLocal<?>>
)。
3. ThreadLocalMap
的內部結構
ThreadLocalMap
是一個自定義的哈希表,主要包含以下結構:
- Entry:
ThreadLocalMap
的內部靜態類,用于存儲鍵值對。鍵是弱引用的ThreadLocal
對象,值是實際數據。而Entry是以數組的形式存在,在源碼中的體現就是Entry數組成員變量table(private Entry[] table;),也就是說每個ThreadLocalMap可以保存多個ThreadLocal作為鍵,值可以設置為任意你想與該ThreadLocal進行關聯的值()。
/*** ThreadLocalMap is a customized hash map suitable only for* maintaining thread local values. No operations are exported* outside of the ThreadLocal class. The class is package private to* allow declaration of fields in class Thread. To help deal with* very large and long-lived usages, the hash table entries use* WeakReferences for keys. However, since reference queues are not* used, stale entries are guaranteed to be removed only when* the table starts running out of space.*/
static class ThreadLocalMap{/*** The entries in this hash map extend WeakReference, using* its main ref field as the key (which is always a* ThreadLocal object). Note that null keys (i.e. entry.get()* == null) mean that the key is no longer referenced, so the* entry can be expunged from table. Such entries are referred to* as "stale entries" in the code that follows.*/static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}/*** The initial capacity -- MUST be a power of two.*/private static final int INITIAL_CAPACITY = 16;/*** The table, resized as necessary.* table.length MUST always be a power of two.*/private Entry[] table;/*** The number of entries in the table.*/private int size = 0;/*** The next size value at which to resize.*/private int threshold;// 部分源碼省略...
}
- 哈希沖突處理:
ThreadLocalMap
使用線性探測法解決哈希沖突。當發生哈希沖突時,會依次檢查下一個位置,直到找到空位置或匹配的鍵。 - 垃圾回收:由于鍵是弱引用,當
ThreadLocal
對象沒有其他強引用時,會被垃圾回收器回收。此時,ThreadLocalMap
的鍵會變成null
,但值仍然存在。因此,需要顯式地調用remove()
方法或依賴ThreadLocalMap
的內部機制來清理這些條目。
4.Thread
的成員變量ThreadLocalMap
每個線程 (Thread 對象) 都有一個 ThreadLocalMap 實例。具體來說,Thread 類有一個 ThreadLocalMap類型的threadLocals 字段,用于保存當前線程的 ThreadLocalMap, 也就是負責管理當前線程的變量副本。因為ThreadLocalMap可以保存多個不同的ThreadLocal對象作為鍵,值為任意內容的鍵值對,所以每個線程可以保存多個變量副本,數量上限取決于ThreadLocal對象的個數。
public
class Thread implements Runnable {// 部分源碼省略.../* ThreadLocal values pertaining to this thread. This map is maintained* by the ThreadLocal class. */ThreadLocal.ThreadLocalMap threadLocals = null;/** InheritableThreadLocal values pertaining to this thread. This map is* maintained by the InheritableThreadLocal class.*/ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;// 部分源碼省略...
}
5. 如何為每個線程提供一個獨立的變量副本,實現線程安全?
在多線程環境中, 每個線程操作 ThreadLocal 類時,只影響到自己線程的 ThreadLocalMap 里面的內容,而不會干擾到其他線程的 ThreadLocalMap。 如果應用程序中只使用一個ThreadLocal,那么每個線程內部的 ThreadLocalMap 都保存著相同的 ThreadLocal對象作為鍵,值可以設置成任意內容。如果應用程序中使用了多個ThreadLocal,那么每個線程內部的 ThreadLocalMap 都保存著多個 ThreadLocal對象 作為鍵,值為 每個ThreadLocal對象所關聯對應的內容,每個線程也就保存了多個獨立的變量副本。因此也就實現了每個線程都有自己的獨立變量副本。
接下來我們看看ThreadLocal的set()方法和get()方法源碼
get()方法
/*** Returns the value in the current thread's copy of this* thread-local variable. If the variable has no value for the* current thread, it is first initialized to the value returned* by an invocation of the {@link #initialValue} method.** @return the current thread's value of this thread-local*/
public T get() {// 獲取當前線程Thread t = Thread.currentThread();// 獲取當前線程的ThreadLocalMapThreadLocalMap map = getMap(t);// 如果當前線程的ThreadLocalMap不為nullif (map != null) {// 從ThreadLocalMap的Entry類型數組table中獲取以當前// ThreadLocal作為鍵的Entry實例ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {// Entry實例e不為null,返回其value@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}// 給當前線程t的ThreadLocalMap設置初始值return setInitialValue();
}/*** Get the entry associated with key. This method* itself handles only the fast path: a direct hit of existing* key. It otherwise relays to getEntryAfterMiss. This is* designed to maximize performance for direct hits, in part* by making this method readily inlinable.** @param key the thread local object* @return the entry associated with key, or null if no such*/
private Entry getEntry(ThreadLocal<?> key) {// 計算索引值,用于在table中定位Entryint i = key.threadLocalHashCode & (table.length - 1);// 獲取當前位置的EntryEntry e = table[i];// 檢查當前位置的Entry是否有效且其存儲的ThreadLocal對象等于keyif (e != null && e.get() == key) {// 直接命中,返回Entryreturn e;} else {// 未命中或Entry已失效,則通過getEntryAfterMiss進一步處理return getEntryAfterMiss(key, i, e);}
}
set()方法
/*** Sets the current thread's copy of this thread-local variable* to the specified value. Most subclasses will have no need to* override this method, relying solely on the {@link #initialValue}* method to set the values of thread-locals.** @param value the value to be stored in the current thread's copy of* this thread-local.
*/
public void set(T value) {// 獲取當前線程Thread t = Thread.currentThread();// 獲取當前線程的ThreadLocalMapThreadLocalMap map = getMap(t);if (map != null)//如果當前線程的ThreadLocalMap不為null// 設置key為當前ThreadLocal的值為valuemap.set(this, value);else//如果當前線程的ThreadLocalMap為nullcreateMap(t, value);//初始化一個ThreadLocalMap,鍵為當前ThreadLocal
}// .../*** Create the map associated with a ThreadLocal. Overridden in* InheritableThreadLocal.** @param t the current thread* @param firstValue value for the initial entry of the map
*/
void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);
}
6. 內存泄露問題
由于ThreadLocalMap
的鍵是弱引用,ThreadLocal
對象被回收后,鍵會變成null
,但值仍然保留在內存中,導致內存泄露。因此,建議在不再使用 ThreadLocal
時顯式調用 remove()
方法,以確保清理數據。
7. 總結
每個線程在使用 ThreadLocal 類時,操作的是自己線程內部的 ThreadLocalMap,這確保了線程之間的隔離:
- 獨立性:每個線程擁有自己的 ThreadLocalMap 實例,因此對 ThreadLocal 的操作不會相互干擾。
- 線程安全:由于每個線程有獨立的 ThreadLocalMap,不存在并發訪問 ThreadLocalMap 的問題,因此操作是線程安全的。
這種設計使得 ThreadLocal 非常適合在多線程環境下使用,用于存儲線程私有的變量,從而避免了線程間的數據共享問題。