關於ThreadLocal的實現原理以及ThreadLocal為什麼會造成記憶體洩露
用處
可以私有化儲存執行緒的變數值
用法
static class ResourceClass {
public final static ThreadLocal<String> RESOURCE_1 =
new ThreadLocal<String>();
public final static ThreadLocal<String> RESOURCE_2 =
new ThreadLocal<String>();
}
static class A {
public void setOne(String value) {
ResourceClass.RESOURCE_1.set(value);
}
public void setTwo(String value) {
ResourceClass.RESOURCE_2.set(value);
}
}
static class B {
public void display() {
System.out.println(ResourceClass.RESOURCE_1.get()
+ ":" + ResourceClass.RESOURCE_2.get());
}
}
public static void main(String []args) {
final A a = new A();
final B b = new B();
for(int i = 0 ; i < 15 ; i ++) {
final String resouce1 = "執行緒-" + I;
final String resouce2 = " value = (" + i + ")";
new Thread() {
public void run() {
try {
a.setOne(resouce1);
a.setTwo(resouce2);
b.display();
}finally {
ResourceClass.RESOURCE_1.remove();
ResourceClass.RESOURCE_2.remove();
}
}
}.start();
}
}
執行結果
執行緒-4: value = (4)
執行緒-2: value = (2)
執行緒-5: value = (5)
執行緒-1: value = (1)
執行緒-0: value = (0)
執行緒-3: value = (3)
執行緒-6: value = (6)
執行緒-7: value = (7)
執行緒-10: value = (10)
執行緒-11: value = (11)
執行緒-9: value = (9)
執行緒-12: value = (12)
執行緒-13: value = (13)
執行緒-8: value = (8)
執行緒-14: value = (14)
可以看到執行緒是交替執行的,但是每個執行緒取到的值卻沒有亂,說明已經做到了執行緒的私有化。
實現原理
看ThreadLocal的set方法原始碼
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);//獲取當前執行緒的ThreadLocalMap
if (map != null)
map.set(this, value);//以當前ThreadLocal物件為鍵,value為值儲存到map中
else
createMap(t, value);
}
ThreadLocal的get方法原始碼
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);//獲取當前執行緒的ThreadLocalMap
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);//以當前ThreadLocal物件為鍵,獲取到map中的物件
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
通過上述原始碼可以看到,實際上是每個Thread執行緒維護著一個ThreadLocalMap,而ThreadLocalMap中的一個鍵即是一個ThreadLocal,value是我們通過set方法傳進去的引數。看下面的圖會好理解一點
也就是說 ThreadLocal 本身並不儲存值,它只是作為一個 key 來讓執行緒從 ThreadLocalMap 獲取 value。
關於記憶體洩漏
為什麼會造成記憶體洩漏
下面字字精華,請耐心讀完
我們上面說了ThreadLocal 本身並不儲存值,它只是作為一個 key儲存到ThreadLocalMap中,但是這裡要注意的是它作為一個key用的是弱引用(什麼是強引用,軟引用,弱引用,虛引用)本處不做闡述。
因為沒有強引用鏈,弱引用在GC的時候可能會被回收。這樣就會在ThreadLocalMap中存在一些key為null的鍵值對(Entry)。因為key變成null了,我們是沒法訪問這些Entry的,但是這些Entry本身是不會被清除的,為什麼呢?因為存在一條強引用鏈。即執行緒本身->ThreadLocalMap->Entry也就是說,恰恰我們在使用執行緒池的時候,執行緒使用完了是會放回到執行緒池迴圈使用的。由於ThreadLocalMap的生命週期和執行緒一樣長,如果沒有手動刪除對應key就會導致這塊記憶體即不會回收也無法訪問,也就是記憶體洩漏。
其實,ThreadLocalMap的設計中已經考慮到這種情況,也加上了一些防護措施:在ThreadLocal的get(),set(),remove()的時候都會清除執行緒ThreadLocalMap裡所有key為null的value。但是這些舉動不能保證記憶體就一定會回收,因為可能這條執行緒被放回到執行緒池裡後再也沒有使用,或者使用的時候沒有呼叫其get(),set(),remove()方法。
為什麼使用弱引用,記憶體洩漏是否是弱引用的鍋?
下面我們分兩種情況討論:
(1)key 使用強引用:引用的ThreadLocal的物件被回收了,但是ThreadLocalMap還持有ThreadLocal的強引用,如果沒有手動刪除,ThreadLocal不會被回收,導致Entry記憶體洩漏。
(2)key 使用弱引用:引用的ThreadLocal的物件被回收了,由於ThreadLocalMap持有ThreadLocal的弱引用,即使沒有手動刪除,ThreadLocal也會被回收。value在下一次ThreadLocalMap呼叫set、get、remove的時候會被清除。
因此:記憶體洩漏歸根結底是由於ThreadLocalMap的生命週期跟Thread一樣長。如果沒有手動刪除對應key就會導致記憶體洩漏,而不是因為弱引用。
如何避免記憶體洩漏
每次使用完ThreadLocal,都呼叫它的remove()方法,清除資料。
注意:並不是所有使用ThreadLocal的地方,都在最後remove(),他們的生命週期可能是需要和專案的生存週期一樣長的,所以要進行恰當的選擇,以免出現業務邏輯錯誤!
寫在最後
看來好像沒有什麼更好的辦法能避免這個問題,小生不才,希望能有指正。
參考原文:https://blog.csdn.net/bntX2jSQfEHy7/article/details/78315161