前言
ThreadLocal為變量在每個線程中都創(chuàng)建了一個副本,所以每個線程可以訪問自己內部的副本變量,不同線程之間不會互相干擾。本文會基于實際場景介紹ThreadLocal如何使用以及內部實現(xiàn)機制。
應用場景
Parameter對象的數(shù)據(jù)需要在多個模塊中使用,如果采用參數(shù)傳遞的方式,顯然會增加模塊之間的耦合性。先看看用ThreadLocal是如何實現(xiàn)模塊間共享數(shù)據(jù)的。
1
2
3
4
5
6
7
8
9
10
|
class Parameter { private static ThreadLocal<Parameter> _parameter= new ThreadLocal<>(); public static Parameter init() { _parameter.set( new Parameter()); } public static Parameter get() { _parameter.get(); } ...省略變量聲明 } |
- 在模塊A中通過Parameter.init初始化。
- 在模塊B或模塊C中通過Parameter.get方法可以獲得同一線程中模塊A已經(jīng)初始化的Parameter對象。
實現(xiàn)原理
從線程Thread的角度來看,每個線程內部都會持有一個對ThreadLocalMap實例的引用,ThreadLocalMap實例相當于線程的局部變量空間,存儲著線程各自的數(shù)據(jù),具體如下:
Entry
Entry繼承自WeakReference類,是存儲線程私有變量的數(shù)據(jù)結構。ThreadLocal實例作為引用,意味著如果ThreadLocal實例為null,就可以從table中刪除對應的Entry。
1
2
3
4
5
6
7
|
class Entry extends WeakReference<ThreadLocal<?>> { Object value; Entry(ThreadLocal<?> k, Object v) { super (k); value = v; } } |
ThreadLocalMap
內部使用table數(shù)組存儲Entry,默認大小INITIAL_CAPACITY(16),先介紹幾個參數(shù):
- size:table中元素的數(shù)量。
- threshold:table大小的2/3,當size >= threshold時,遍歷table并刪除key為null的元素,如果刪除后size >= threshold*3/4時,需要對table進行擴容。
ThreadLocal.set() 實現(xiàn)
1
2
3
4
5
6
7
8
9
10
11
|
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 getMap(Thread t) { return t.threadLocals; } |
從上面代碼中看出來:
- 從當前線程Thread中獲取ThreadLocalMap實例。
- ThreadLocal實例和value封裝成Entry。
接下去看看Entry存入table數(shù)組如何實現(xiàn)的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
private void set(ThreadLocal<?> key, Object value) { 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)]) { ThreadLocal<?> k = e.get(); if (k == key) { e.value = value; return ; } if (k == null ) { replaceStaleEntry(key, value, i); return ; } } tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); } |
1.通過ThreadLocal的nextHashCode方法生成hash值。
1
2
3
4
|
private static AtomicInteger nextHashCode = new AtomicInteger(); private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); } |
從nextHashCode方法可以看出,ThreadLocal每實例化一次,其hash值就原子增加HASH_INCREMENT。
2.通過 hash & (len -1) 定位到table的位置i,假設table中i位置的元素為f。
3.如果f != null,假設f中的引用為k:
- 如果k和當前ThreadLocal實例一致,則修改value值,返回。
- 如果k為null,說明這個f已經(jīng)是stale(陳舊的)的元素。調用replaceStaleEntry方法刪除table中所有陳舊的元素(即entry的引用為null)并插入新元素,返回。
- 否則通過nextIndex方法找到下一個元素f,繼續(xù)進行步驟3。
4.如果f == null,則把Entry加入到table的i位置中。
5.通過cleanSomeSlots刪除陳舊的元素,如果table中沒有元素刪除,需判斷當前情況下是否要進行擴容。
table擴容
如果table中的元素數(shù)量達到閾值threshold的3/4,會進行擴容操作,過程很簡單:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
private void resize() { Entry[] oldTab = table; int oldLen = oldTab.length; int newLen = oldLen * 2 ; Entry[] newTab = new Entry[newLen]; int count = 0 ; for ( int j = 0 ; j < oldLen; ++j) { Entry e = oldTab[j]; if (e != null ) { ThreadLocal<?> k = e.get(); if (k == null ) { e.value = null ; // Help the GC } else { int h = k.threadLocalHashCode & (newLen - 1 ); while (newTab[h] != null ) h = nextIndex(h, newLen); newTab[h] = e; count++; } } } setThreshold(newLen); size = count; table = newTab; } |
- 新建新的數(shù)組newTab,大小為原來的2倍。
- 復制table的元素到newTab,忽略陳舊的元素,假設table中的元素e需要復制到newTab的i位置,如果i位置存在元素,則找下一個空位置進行插入。
ThreadLocal.get() 實現(xiàn)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
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; } } return setInitialValue(); } private Entry getEntry(ThreadLocal<?> key) { int i = key.threadLocalHashCode & (table.length - 1 ); Entry e = table[i]; if (e != null && e.get() == key) return e; else return getEntryAfterMiss(key, i, e); } |
獲取當前的線程的threadLocals。
- 如果threadLocals不為null,則通過ThreadLocalMap.getEntry方法找到對應的entry,如果其引用和當前key一致,則直接返回,否則在table剩下的元素中繼續(xù)匹配。
- 如果threadLocals為null,則通過setInitialValue方法初始化,并返回。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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 ) expungeStaleEntry(i); else i = nextIndex(i, len); e = tab[i]; } return null ; } |
總結
希望通過本文的介紹,大家可以對ThreadLocal有一個更加直觀清晰的認識。
以上就是本文的全部內容,希望本文的內容對大家的學習或者工作能帶來一定的幫助,同時也希望多多支持服務器之家!
原文鏈接:http://www.cnblogs.com/houziwty/p/6392775.html