在多執行緒的情況下,ThreadLocal提供了一個種為每個執行緒訪問相同的變數,并且執行緒對變數的更新互不影響的機制,也是物件實作執行緒安全的一種方式,
ThreadLocal的實作機制
我們常用的方法有get、set和initialValue,這次將會圍繞這幾個方法的原始碼進行深入決議
- get方法
// 獲取元素
public T get() {
// 當前執行緒
Thread t = Thread.currentThread();
// 通過當前執行緒獲取ThreadLocalMap物件
ThreadLocalMap map = getMap(t);
if (map != null) {
// 獲取Entry,其中key為ThreadLocal物件自身
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value; // 獲取物件的值
return result;
}
}
// 回傳initialValue的值
return setInitialValue();
}
首先,通過當前執行緒物件獲取ThreadLocalMap物件,然后以ThreadLocal物件自身為key獲取ThreadLocalMap.Entry,最后在獲取Entry中的value
代碼的邏輯非常簡單,我們再來看看getMap和map.getEntry方法
- getMap方法
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
threadLocals是Thread的一個屬性
public class Thread implements Runnable {
// ......
// threadLocals是Thread的一個屬性
ThreadLocal.ThreadLocalMap threadLocals = null;
}
- map.getEntry方法
ThreadLocalMap是ThreadLocal物件的一個內部類,Entry是ThreadLocalMap的一個內部類
// ThreadLocal的內部類
static class ThreadLocalMap {
// Entry是ThreadLocalMap的內部類,是一個弱參考物件
static class Entry extends WeakReference<ThreadLocal<?>> {
// ThreadLocal中的value
Object value;
// Entry的Key為ThreadLocal物件
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = https://www.cnblogs.com/pinxiong/p/v;
}
}
// Entry陣列,用來存放一個執行緒的多個ThreadLocal變數
private Entry[] table;
// 根據ThreadLocal來獲取對應的value
private Entry getEntry(ThreadLocal<?> key) {
// 通過hash演算法獲取key在陣列中對應的下標
int i = key.threadLocalHashCode & (table.length - 1);
// 獲取下標對應的Entry物件
Entry e = table[i];
// 獲取value
if (e != null && e.get() == key)
return e;
else
// 當key不存在時獲取值,有2中可能
// 1. 可能過期了
// 2. 可能擴縮容
return getEntryAfterMiss(key, i, e);
}
}
當key過期了如何獲取值
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
ThreadLocal<?> k = e.get();
// 如果key存在,直接回傳value
if (k == key)
return e;
// 如果key為空說明已經過期了,需要清除
if (k == null)
expungeStaleEntry(i);
else
// 獲取下一個key,看看能否找到
// 這是由于清除已經過期的key,
// 改變了Entry陣列的size引起的位置變更
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
- 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);
}
先獲取ThreadLocalMap物件,然后在以ThreadLocal為key,將value設定到ThreadLocalMap物件中
- map.set方法
// 將ThreadLocal對應的value存盤到ThreadLocalMap物件中
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
// 計算table中的下標
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
// 獲取ThreadLocal物件
ThreadLocal<?> k = e.get();
// 如果Entry陣列中存在ThreadLocal物件,則替換之前的值
if (k == key) {
e.value = https://www.cnblogs.com/pinxiong/p/value;
return;
}
// 去掉過期的ThreadLocal物件
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
// Entry陣列中不存在ThreadLocal物件,創建一個新的Entry物件
tab[i] = new Entry(key, value);
int sz = ++size;
// 清除過期的物件
if (!cleanSomeSlots(i, sz) && sz >= threshold)
// Entry陣列的大小改變以后重新計算hash
rehash();
}
- createMap方法
void createMap(Thread t, T firstValue) {
// 當執行緒的threadLocals為null時,為執行緒初始化一個ThreadLocalMap物件
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
- initialValue方法
// 可以通過重寫該方法來回傳默認值
protected T initialValue() {
return null;
}
ThreadLocal記憶體泄漏問題
首先看一下ThreadLocal中物件的參考關系圖

從ThreadLocal中物件的參考關系來看,Thread、ThreadLocalMap、Entry物件之間都是強參考,如果可能出現記憶體泄漏那就是ThreadLocal物件弱參考引起的,
什么時候會發生記憶體泄漏
當ThreadLocal實體不在有強參考指向,只有弱參考存在,且GC回收了這部分空間時,也就是Entry物件中的key被回收了,但是value還沒有被回收,這時會出現記憶體泄漏,因為value無法得到釋放,
如何避免記憶體泄漏
ThreadLocalMap中是通過expungeStaleEntry將key為null的物件對應的value也設定為null
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
tab[staleSlot].value = https://www.cnblogs.com/pinxiong/p/null;
tab[staleSlot] = null;
size--;
Entry e;
int i;
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
// 如果key為null,會將value也設定成null
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
所以只要呼叫expungeStaleEntry方法,且key為null時就可以回收掉value了,我們可以通過呼叫ThreadLocal的remove方法進行釋放
避免ThreadLocal出現記憶體泄漏的方式有:
- 呼叫
ThreadLocal的remove方法 - 將
ThreadLocal變數定義成static型別的,對ThreadLocal的強參考不會消失,所以也不存在記憶體泄漏的問題,但是可能會有所浪費
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/143913.html
標籤:Java
上一篇:Java中的參考
下一篇:JVM垃圾回收的程序
