jdk源碼註解中有這樣一段描述:
數組
This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its {@code get} or {@code set} method) has its own, independently initialized copy of the variable. {@code ThreadLocal} instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).
緩存
這個類提供線程局部變量。這些變量與其正常的對應方式不一樣,由於訪問一個的每一個線程(經過其get或set方法)都有本身獨立初始化的變量副本。 ThreadLocal實例一般是但願將狀態與線程關聯的類中的私有靜態字段(例如,用戶ID或事務ID)
安全
須要明確的是ThreadLocal不是用於解決共享變量的問題的,也不是爲了協調線程同步而存在,而是爲了方便每一個線程處理本身的狀態而引入的一個機制多線程
public class SeqCount {
private static ThreadLocal seqCount = new ThreadLocal(){
// 實現initialValue()
public Integer initialValue() {
return 0;
}
};
public int nextSeq(){
seqCount.set(seqCount.get() + 1);
return seqCount.get();
}
public void remove() {
seqCount.remove();
}
public static void main(String[] args){
SeqCount seqCount = new SeqCount();
SeqThread thread1 = new SeqThread(seqCount);
SeqThread thread2 = new SeqThread(seqCount);
SeqThread thread3 = new SeqThread(seqCount);
SeqThread thread4 = new SeqThread(seqCount);
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
private static class SeqThread extends Thread{
private SeqCount seqCount;
SeqThread(SeqCount seqCount){
this.seqCount = seqCount;
}
public void run() {
try {
for(int i = 0 ; i < 3 ; i++){
System.out.println(Thread.currentThread().getName() + " seqCount :" + seqCount.nextSeq());
}
} finally {
seqCount.remove();
}
}
}
}
複製代碼
運行結果:ide
Thread-0 seqCount :1
Thread-0 seqCount :2
Thread-0 seqCount :3
Thread-1 seqCount :1
Thread-1 seqCount :2
Thread-1 seqCount :3
Thread-3 seqCount :1
Thread-3 seqCount :2
Thread-3 seqCount :3
Thread-2 seqCount :1
Thread-2 seqCount :2
Thread-2 seqCount :3
複製代碼
從結果能夠得知,ThreadLocal確實是能夠達到線程隔離機制,保證了變量的安全性this
ThreadLocal是爲每個線程建立一個單獨的變量副本,因此每一個線程均可以獨立地改變本身所擁有的變量副本,而不會影響其餘線程所對應的副本。從其幾個方法入手
spa
public void set(T value) {
// 獲取當前線程
Thread t = Thread.currentThread();
// 經過當前線程實例獲取ThreadLocalMap對象
ThreadLocalMap map = getMap(t);
// 若map不爲null,則以當前threadLocal爲鍵,value爲值存放
if (map != null)
map.set(this, value);
// 若map爲null,則建立ThreadLocalMap,以當前threadLocal爲鍵,value爲值
else
createMap(t, value);
}
複製代碼
獲取當前線程實例,調用getMap()獲取此線程的ThreadLocalMap線程
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
複製代碼
而後判斷map是否爲null,若爲null則還需建立threadLocalMap,以當前threadLocal爲鍵,value爲值存放在threadLocalMap中,若不爲null直接存儲便可code
public T get() {
// 獲取當前線程
Thread t = Thread.currentThread();
// 獲取線程關聯的ThreadLocalMap
ThreadLocalMap map = getMap(t);
// 若map不爲null,從map中獲取以當前threadLocal實例爲key的數據
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 若map爲null或者entry爲null,則調用此方法初始化
return setInitialValue();
}
複製代碼
get方法獲取當前線程關聯的ThreadLocalMap。若map不爲null,以threadLocal實例爲key獲取數據;若map爲null或entry爲null調用setInitialValue()方法orm
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
複製代碼
與set方法差很少,但多了initialValue()方法,此方法須要子類重寫
protected T initialValue() {
return null;
}
複製代碼
public void remove() {
// 根據當前線程獲取其所關聯的ThreadLocalMap
ThreadLocalMap m = getMap(Thread.currentThread());
// 若map不爲null,刪除以當前threadLocal爲key的數據
if (m != null)
m.remove(this);
}
複製代碼
從ThreadLocal那幾個核心方法來看,其實現都基於內部類ThreadLocalMap
// 初始化容量
private static final int INITIAL_CAPACITY = 16;
// 哈希表
private Entry[] table;
// 元素個數
private int size = 0;
// 擴容閾值(threshold = 底層哈希表table的長度 len * 2 / 3)
private int threshold;
複製代碼
static class Entry extends WeakReference> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
複製代碼
從源碼中能夠得知Entry的key是Threadlocal,而且Entry繼承WeakReference弱引用。注意Entry中並無next屬性,相對於HashMap採用鏈地址法處理衝突,ThreadLocalMap採用開放定址法
private void set(ThreadLocal key, Object value) {
Entry[] tab = table;
int len = tab.length;
// 根據ThreadLocal的hashcode值,尋找對應Entry在數組中的位置
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal k = e.get();
// 若找到對應key,替換舊值返回
if (k == key) {
e.value = value;
return;
}
// 若key == null,由於e!=null確定存在entry
// 說明以前的ThreadLocal對象已經被回收
if (k == null) {
// 替換舊entry
replaceStaleEntry(key, value, i);
return;
}
}
// 建立新entry
tab[i] = new Entry(key, value);
// 元素個數+1
int sz = ++size;
// cleanSomeSlots 清除舊Entry(key == null)
// 若是沒有要清除的數據,元素個數仍然大於閾值則擴容
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
複製代碼
每一個ThreadLocal對象都有一個hash值threadLocalHashCode,每初始化一個ThreadLocal對象,hash值就增長一個固定的大小0x61c88647。在插入過程當中先根據threadlocal對象的hash值,定位哈希表的位置:
一、若此位置是空的,就建立一個Entry對象放在此位置上,調用cleanSomeSlots()方法清除key爲null的舊entry,若沒有要清除的舊entry則判斷是否須要擴容
二、若此位置已經有Entry對象了,若是這個Entry對象的key正好是所要設置的key或key爲null,則替換value值
三、若此位置Entry對象的key不符合條件,尋找哈希表此位置+1(若到達哈希表尾則從頭開始)
咱們能夠發現ThreadLocalMap採用了開放定址法來解決衝突,一旦發生了衝突,就去尋找下一個空的散列地址,而HashMap採用鏈地址法解決衝突在原位置利用鏈表處理
private Entry getEntry(ThreadLocal key) {
// 定位
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
// 若此位置不爲空且與entry的key返回entry對象
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
複製代碼
理解了set,getEntry很好理解。先根據threadlocal對象的hash值,定位哈希表的位置。若此位置entry的key和查找的key相同的話就直接返回這個entry,若不符合調用getEntryAfterMiss()繼續向後找,getEntryAfterMiss方法以下:
private Entry getEntryAfterMiss(ThreadLocal key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
ThreadLocal k = e.get();
// 找到和所需key相同的entry則返回
if (k == key)
return e;
// 處理key爲null的entry
if (k == null)
expungeStaleEntry(i);
else
// 繼續找下一個
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
複製代碼
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)]) {
// 若找到所需key
if (e.get() == key) {
// 將entry的key置爲null
e.clear();
// 將entry的value置爲null同時entry置空
expungeStaleEntry(i);
return;
}
}
}
複製代碼
定位在哈希表的位置,找到相同key的entry,調用clear方法將key置爲null,調用expungeStaleEntry方法刪除對應位置的過時實體,並刪除此位置後key = null的實體
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// 將此位置的entry對象置空以及value置空
tab[staleSlot].value = null;
tab[staleSlot] = null;
// 元素個數-1
size--;
// Rehash until we encounter null
Entry e;
int i;
// 清除此位置後key爲null的entry對象以及rehash位置不一樣的entry直至有位置爲空爲止
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);
if (h != i) {
tab[i] = null;
// Unlike Knuth 6.4 Algorithm R, we must scan until
// null because multiple entries could have been stale.
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
複製代碼
先附上四種引用與gc關係
引用類型 | 回收機制 | 用途 | 生存時間 |
強引用 | 從不回收 | 對象狀態 | JVM中止運行時 |
軟引用 | 內存不足時回收 | 對象緩存 | 內存不足時終止 |
弱引用 | 對象不被引用時回收 | 對象緩存 | GC後終止 |
虛引用 | 對象不被引用時回收 | 跟蹤對象的垃圾回收 | 垃圾回收後終止 |
Son son = new Son();
Parent parent = new Parent(son);
複製代碼
當咱們把son置空,因爲parent持有son的引用且parent是強引用,因此gc並不回收son所分配的內存空間,這就致使了內存泄露
若是是弱引用那麼上述例子,GC就會回收son所分配的內存空間。而ThreadLocalMap採用ThreadLocal弱引用做爲key,雖然ThreadLocal是弱引用GC會回收這部分空間即key被回收,可是value卻存在一條從Current Thread過來的強引用鏈。所以只有當Current Thread銷燬時,value才能 獲得釋放
那麼如何有效的避免呢?
在上述中咱們能夠看到ThreadLocalMap中的set/getEntry方法中,會對key爲null(即ThreadLocal爲null)進行判斷,若是爲null的話,那麼是會對value置爲null的。固然也能夠經過調用ThreadLocal的remove方法進行釋放。
ThreadLocal不是用來解決共享對象的多線程訪問問題,而是爲了方便每一個線程處理本身的狀態而引入的一個機制。它爲每個線程都提供一份變量的副本,從而實現同時訪問而互不影響。另外ThreadLocal可能存在內存泄漏問題,使用完ThreadLocal以後,最好調用remove方法
www.jianshu.com/p/377bb8408…
www.jianshu.com/p/ee8c9dccc…
cmsblogs.com/?p=2442