概述 ThreadLocal 意為本地線程變數,即該變數只屬於當前線程,對其他線程隔離 我們知道,一個普通變數如果被多線程訪問會存在存線上程安全問題,這時我們可以使用 Synchronize 來保證該變數某一時刻只能有一個線程訪問,從而解決併發安全問題 但如果這個變數並不需要被共用,那麼就可以使用 ...
概述
ThreadLocal 意為本地線程變數,即該變數只屬於當前線程,對其他線程隔離
我們知道,一個普通變數如果被多線程訪問會存在存線上程安全問題,這時我們可以使用 Synchronize 來保證該變數某一時刻只能有一個線程訪問,從而解決併發安全問題
但如果這個變數並不需要被共用,那麼就可以使用 ThreadLocal 為每個線程提供一個完全獨立的變數副本,每個線程只操作自身擁有的副本,彼此互不幹擾
簡而言之,Synchronized 用於線程間的數據共用,同步機制採用採用時間換空間的方式,而 ThreadLocal 則用於線程間的數據隔離,採用空間換時間的方式
ThreadLocal 使用
public class ThreadLocalTest {
// 有個User對象需要在不同線程之間進行隔離訪問,可以定義ThreadLocal如下
static ThreadLocal<User> userThreadLocal = new ThreadLocal<>();
// 設置線程本地變數的內容
public void setUser(User user) {
userThreadLocal.set(user);
}
public void getUser() {
// 獲取線程本地變數的內容
User user = userThreadLocal.get();
user.setUsername(user.getUsername + "-" + Thread.currentThread().getName());
System.out.println(user.getUsername());
// 移除線程本地變數
userThreadLocal.remove();
}
}
測試代碼如下
public class DemoTest {
public static void main(String[] args) {
ThreadLocalTest threadLocalTest = new ThreadLocalTest();
for(int i = 0; i < 100; i++) {
Thread thread = new Thread(() -> {
User user = new User();
user.setUsername("小明");
threadLocalTest.setUser(user);
threadLocalTest.getUser();
});
thread.setName(String.valueOf(i));
thread.start();
}
}
}
ThreadLocal 原理
首先,Java 中的線程是一個 Thread 類的實例對象,對象可以定義私有的成員變數,這也是 ThreadLocal 能實現線程本地變數的基礎
在 Thread 類中定義了一個 Map 類型的成員變數,用來保存該線程的所有本地變數
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocalMap 的 Entry 的定義如下,key 為 ThreadLocal 對象,v 就是我們要線上程之間隔離的對象
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
ThreadLocal::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 方法賦值時,首先會獲取當前線程 thread,並獲取 thread 線程的 ThreadLocalMap 屬性。如果 map 屬性不為空,則直接更新 value 值,key 就是自身的 ThreadLocal,如果 map 為空,則實例化 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();
}
使用 get 方法獲取值,也是首先獲取當前線程,再獲取線程 ThreadLocalMap,如果 map 不為空就用自身 ThreadLocal 為 key 從 map 獲取對應的 value
ThreadLocal::remove 方法的源碼如下:
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null) {
m.remove(this);
}
}
remove 方法也是獲取當前線程並獲取 ThreadLocalMap,再將自身 ThreadLocal 為 key 對應的 value 移除
綜合以上,我們知道 ThreadLocalMap 是線程的一個屬性值,用來保存該線程的本地變數。ThreadLocal 能操作當前線程的 ThreadLocalMap,具體做法是以自身為 key 在 map 中存取值。因為每個線程的 ThreadLocalMap 都是獨立的,所以每次使用 ThreadLocal 存取值都僅限於當前的線程,不會影響其他線程
ThreadLocal 記憶體泄露問題
記憶體泄露問題:指程式中動態分配的堆記憶體由於某種原因沒有被釋放或者無法釋放,造成系統記憶體的浪費,導致程式運行速度減慢或者系統奔潰等嚴重後果。記憶體泄露堆積將會導致記憶體溢出
觀察使用 ThreadLocal 時的記憶體佈局
當 ThreadLocal Ref 被回收了,由於在 Entry 使用的是強引用,在 Current Thread 還存在的情況下就存在著到達 Entry 的引用鏈,無法清除掉 ThreadLocal 的內容,同時 Entry 的 value 也同樣會被保留,也就是說使用了強引用會出現記憶體泄露問題
為此,ThreadLocal 在 Entry 使用了弱引用
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
// Entry繼承WeakReference,將key傳入父級構造方法,從而形成弱引用
super(k);
value = v;
}
}
再觀察使用軟引用後的記憶體佈局
當 ThreadLocal Ref 被回收,由於在 Entry 使用的是弱引用,因此在下次垃圾回收的時候就會將 ThreadLocal 對象清除
但由於 ThreadLocalMap 仍然存在 Current Thread Ref 這個強引用,Entry 中 value 的值仍然無法清除,還是存在記憶體泄露的問題,雖然當線程生命周期結束,Current Thread Ref 這個強引用也會隨之消失,value 會在下一次垃圾回收被清除,但如果使用線程池,那麼線程會被重新放回線程池等待復用,那麼強引用就會一直存在。因此,我們在每次在使用完之後需要手動的 remove 掉 Entry 對象