線程本地變量保存,與并發(fā)實(shí)際上并沒關(guān)系
即:在線程中保存一個(gè)局部變量,在該線程執(zhí)行過程,獲取時(shí)一定能獲取到上次設(shè)置的值(前提不進(jìn)行remove或者設(shè)置為null)
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;
}
}
//線程中還沒創(chuàng)建保存變量的Map或者沒有找到,則直接初始化默認(rèn)變量值并創(chuàng)建Map
return setInitialValue();
}
//獲取value
private Entry getEntry(ThreadLocal<?> key) {
//通過Hash獲取指定的下標(biāo)
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
//沒找到(hash沖突了)
return getEntryAfterMiss(key, i, e);
}
//通過開放定址法進(jìn)行查找(存在hash時(shí),查找該下標(biāo)后一位的值進(jìn)行判斷)
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)
//發(fā)現(xiàn)有Entry不為空,key為空的節(jié)點(diǎn),可能key已經(jīng)被垃圾回收了,值還沒被回收,因此需要清除
expungeStaleEntry(i);
else
//當(dāng)前下標(biāo)的下一位
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
//清除staleSlot及其節(jié)點(diǎn)后key=null的Entry,同時(shí)返回最近一個(gè)Entry為空的節(jié)點(diǎn)的下標(biāo)
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
//先把當(dāng)前給清了
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
Entry e;
int i;
//依次向后查找,如果發(fā)現(xiàn)key還有為空的,依舊清除
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);
//按正常來說,k在該位置但通過hash判斷不是在該位置,說明k元素也是存在hash沖突被移過來的
//將其歸為到原來應(yīng)該待的地方(因?yàn)檫@個(gè)位置可能已經(jīng)騰出來了),如果已經(jīng)存在沖突,則依次向后查找一個(gè)空位置放進(jìn)去
if (h != i) {
tab[i] = null;
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
set過程
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
//先進(jìn)行hash沖突判斷
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
//該位置雖然被人占坑,但是key已經(jīng)過期,可以替換為新的key和value
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//沒有hash沖突或一直存在hash沖突(即上述過程沒有成功)
tab[i] = new Entry(key, value);
int sz = ++size;
//新增或刪除時(shí),會重新清理過期的數(shù)據(jù)
//該處表示沒有清除掉過期的數(shù)據(jù)(所有數(shù)據(jù)都有用),則會進(jìn)行擴(kuò)容
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
//替換元素操作,同時(shí)盡可能的清理掉已經(jīng)"過期"的數(shù)據(jù)
private void replaceStaleEntry(ThreadLocal<?> key, Object value,
int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e;
//應(yīng)該放在staleSlot位置,但是先向前操作一波,看看是否還有key=null的Entry節(jié)點(diǎn)
int slotToExpunge = staleSlot;
for (int i = prevIndex(staleSlot, len); (e = tab[i]) != null; i = prevIndex(i, len)) {
if (e.get() == null) {
slotToExpunge = i;
}
}
// 向后查找,雖然要放在staleSlot位置,但是前面也僅僅判斷該位置為null(這個(gè)位置可能是其他元素先占的,只是后面被清除了而已)
//因此需要向后查找,看看是否已經(jīng)真的存在當(dāng)前key的元素
for (int i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
//發(fā)現(xiàn)后面確實(shí)存在
if (k == key) {
e.value = value;
//調(diào)換位置 注意tab[staleSlot]是個(gè)key為null的不為空的Entry
tab[i] = tab[staleSlot];
tab[staleSlot] = e;
//此處表示在staleSlot節(jié)點(diǎn)前并沒發(fā)現(xiàn)key=null的Entry節(jié)點(diǎn)存在,且staleSlot~i之間也沒有發(fā)現(xiàn)key=null的Entry節(jié)點(diǎn)
if (slotToExpunge == staleSlot) {
//目前也只是遍歷(0-slotToExpunge[都不為空])
//甚至staleSlot-i之間都可能存在key=null的Entry存在,
//同樣i-len之間依舊可能存在key=null的Entry存在
slotToExpunge = i;
}
//expungeStaleEntry清除從i到len之間key=null的Entry節(jié)點(diǎn)的數(shù)據(jù)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}
//當(dāng)前節(jié)點(diǎn)為空,且(0-slotToExpunge)全部有值,記錄第一次出現(xiàn)key=null的Entry節(jié)點(diǎn)的位置,方便清除
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}
//自始至終沒找到后面與之相同key
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);
//但是在遍歷的過程中找到了key=null的Entry節(jié)點(diǎn),清除操作
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
//嘗試遍歷從i-n之間的Entry節(jié)點(diǎn),發(fā)現(xiàn)"過期"的entry則刪除
private boolean cleanSomeSlots(int i, int n) {
boolean removed = false;
Entry[] tab = table;
int len = tab.length;
do {
i = nextIndex(i, len);
Entry e = tab[i];
//發(fā)現(xiàn)有過期的元素,則直接從len/2處向后遍歷
if (e != null && e.get() == null) {
n = len;
removed = true;
i = expungeStaleEntry(i);
}
//此處個(gè)人感覺減少無用的遍歷
} while ( (n >>>= 1) != 0);
return removed;
}
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();
//清除自身的同時(shí)也會將i之后的某些key=null的Entry清除了
expungeStaleEntry(i);
return;
}
}
}
expungeStaleEntry與cleanSomeSlots總結(jié)
-
expungeStaleEntry
遍歷 從指定下標(biāo)x到最近一個(gè)Entry(下標(biāo)為y) 即 (x-y)之間所有Entry是否存在key=null的元素,如果存在則清除,注意 y的下標(biāo)不是人為可控的(只要發(fā)現(xiàn)有Entry為空則暫停后續(xù)的清除操作) -
cleanSomeSlots
清除(x-n)節(jié)點(diǎn)所有key=null的Entry節(jié)點(diǎn)的值,n的位置是可控的
內(nèi)存溢出問題(個(gè)人總結(jié))
由于
ThreadLocalMap
中Entry的key是弱引用,而key=ThreadLocal,在一般情況下 我們定義一個(gè)ThreadLocal都是staitc final的(官方也這么建議),因此key=null的可能性幾乎為零,弱引用本身的作用無法提現(xiàn)出來,key和value都是強(qiáng)引用 (還請大佬能指明一下)內(nèi)存溢出主要是在線程池中,我們一般使用Tomcat最為web容器,而Tomcat接收請求后交給線程池來處理我們的業(yè)務(wù)請求,因此線程無法被銷毀,線程變量永遠(yuǎn)不會清空會造成內(nèi)存泄露,尤其是項(xiàng)目中大規(guī)模使用ThreadLocal或者存儲過多數(shù)據(jù)時(shí)
盡管set get 操作會在一定情況下清除key=null value不為空的數(shù)據(jù),但是由于我們經(jīng)常是用final,很少存在key=null的情況
因此強(qiáng)制建議在使用完線程變量后調(diào)用remove()方法進(jìn)行清除