分析ThreadLocal的實現原理
阿新 • • 發佈:2018-12-15
ThreadLocal
是開發中常會使用的一個工具,從類的名字就可以看出,它為執行緒提供本地變數,即:每個執行緒私有的資料。下面直接進入原始碼。
1、使用方法:
// Entity 存放執行緒要儲存的資訊
ThreadLocal<Entity> threadLocal = new ThreadLocal<Entity>();
//為執行緒設定私有資料
threadLocal.set(new Entity());
//拿出執行緒私有資料
threadLocal.get();
2、為執行緒設定私有資料:
①ThreadLocal的set()
方法
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
//該執行緒的threadLocals已經初始化過,則呼叫ThreadLocalMap的set()方法,將資料push進來,否則呼叫createMap()函式初始化該變數
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap (Thread t) {
//返回該執行緒的threadLocals變數
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
//初始化該執行緒的threadLocals變數
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
public class Thread implements Runnable {
//每一個執行緒會有唯一的一個ThreadLocalMap型別變數,用來儲存該執行緒所有的私有資料
ThreadLocal.ThreadLocalMap threadLocals = null;
}
②ThreadLocalMap
的set()
方法,從上面知道每一個執行緒有個ThreadLocalMap
的例項,用來儲存該執行緒的私有資料(一個執行緒可能會存很多不同型別的私有資料)。
//ThreadLocalMap 構造方法
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//資料是儲存在一個數組裡面的,初始長度為16
table = new Entry[INITIAL_CAPACITY];
//計算資料放在陣列的哪個位置,是根據ThreadLocal 的threadLocalHashCode值來計算的
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
//以ThreadLocal例項為key,放入實際的資料
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
//Entry類實際儲存著資料,繼承 WeakReference
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
//儲存放進來的執行緒私有資料
value = v;
}
}
//ThreadLocalMap set方法
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
//計算資料放入的位置
int i = key.threadLocalHashCode & (len-1);
//檢測hash衝突,如果該位置已經放入了一個數據(e!=null),則線性地向後查詢第一個合適的位置
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
//Entry 繼承自WeakReference,所以用get()方法,可以直接拿到需要的key(ThreadLocal的例項)
ThreadLocal<?> k = e.get();
//找到一個位置,k和當前要插入的key值相同,更新該位置的資料
if (k == key) {
e.value = value;
return;
}
//找到一個位置已經有資料,但是key為null,則替換掉廢棄的key
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//將key-value儲存到Entry中,放在陣列的i位置
tab[i] = new Entry(key, value);
int sz = ++size;
//判斷是否需要擴充套件陣列,這裡是長度大於陣列長度的2/3時擴充套件陣列
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
以上就將某個資料儲存到執行緒私有資料中了,總結一下:Thread
中有個ThreadLocalMap
例項,ThreadLocalMap
中有個陣列:Entry[] table;
,Entry
中實際儲存了資料value。也就是說一個執行緒可以有n多個私有資料,存放在ThreadLocalMap
中,每一個私有資料對應一個ThreadLocal
例項和一個Entry
例項。
3、取出資料:
①ThreadLocal
的get()方法:
public T get() {
Thread t = Thread.currentThread();
//還是首先要拿到ThreadLocalMap例項
ThreadLocalMap map = getMap(t);
if (map != null) {
//以自己為key,從map中取出實際儲存的資料,上面說過:資料是以ThreadLocal為key,實際資料為value儲存到Entry物件中的
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
①ThreadLocalMap
的getEntry()
方法:
private Entry getEntry(ThreadLocal<?> key) {
//計算陣列下標,和放入資料時一樣
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
//當前位置有資料,且key和要需要的一致,說明該位置儲存了要的資料
if (e != null && e.get() == key)
return e;
else
//雜湊衝突,根據需要的key線性向後查詢
return getEntryAfterMiss(key, i, e);
}
以上就是ThreadLocal
儲存執行緒私有資料的大致實現原理,瞭解這些,相信對以後使用ThreadLocal
會有積極的幫助。