在平常開(kāi)發(fā)的時(shí)候擅笔,經(jīng)常使用到線程本地變量磨德,這種類型的變量會(huì)在每個(gè)線程中都有一份,互相不會(huì)產(chǎn)生影響剂公,這樣來(lái)解決多線程并發(fā)問(wèn)題希俩。
那么是如何實(shí)現(xiàn)的呢?
一. ThreadLocal<T>
1.1 例子
private static final ThreadLocal<AtomicInteger> threadLocal = new ThreadLocal<AtomicInteger>(){
@Override
protected AtomicInteger initialValue() {
AtomicInteger result = new AtomicInteger(0);
System.out.println("創(chuàng)建 AtomicInteger("+result.hashCode()+") thread:"+Thread.currentThread().getName());
return result;
}
};
public static void main(String[] args) {
for (int index = 0; index < 2; index++) {
new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println(threadLocal.get().incrementAndGet()
+" thread:"+Thread.currentThread().getName());
}
}).start();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
運(yùn)行結(jié)果
創(chuàng)建 AtomicInteger(952204834) thread:Thread-0
創(chuàng)建 AtomicInteger(471787139) thread:Thread-1
1 thread:Thread-0
2 thread:Thread-0
3 thread:Thread-0
4 thread:Thread-0
1 thread:Thread-1
5 thread:Thread-0
2 thread:Thread-1
3 thread:Thread-1
4 thread:Thread-1
5 thread:Thread-1
從運(yùn)行結(jié)果可以得出每個(gè)線程都創(chuàng)建了
AtomicInteger
實(shí)例纲辽,因此彼此不會(huì)產(chǎn)生影響颜武。
ThreadLocal<T>
可以看出兩部分:
- 一個(gè)是
ThreadLocal
對(duì)象實(shí)例(即例子中的threadLocal
)璃搜,這個(gè)實(shí)例只有一個(gè),多線程共享的盒刚。 - 另一個(gè)是由
ThreadLocal
對(duì)象實(shí)例創(chuàng)建的對(duì)象(即例子中的AtomicInteger
)腺劣,這個(gè)是每個(gè)線程都會(huì)創(chuàng)建并持有。
因此你會(huì)發(fā)現(xiàn):
- 每個(gè)線程可以根據(jù)
ThreadLocal
對(duì)象實(shí)例threadLocal
來(lái)查找對(duì)應(yīng)的所創(chuàng)建的對(duì)象AtomicInteger
因块,相當(dāng)于key->value
的鍵值映射關(guān)系橘原。- 而每個(gè)線程可以有多個(gè)
ThreadLocal
對(duì)象實(shí)例,即多個(gè)key
涡上。- 那么我們可以斷定趾断,每個(gè)線程肯定有一個(gè)集合對(duì)象來(lái)存儲(chǔ)上面的多個(gè)
key->value
鍵值映射關(guān)系,其實(shí)就是Thread
中成員屬性ThreadLocal.ThreadLocalMap threadLocals
吩愧。
1.2 get
方法
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
public T get() {
// 先獲取當(dāng)前線程
Thread t = Thread.currentThread();
// 從當(dāng)前線程中獲取存儲(chǔ)鍵值映射關(guān)系的Map
ThreadLocal.ThreadLocalMap map = getMap(t);
if (map != null) {
// 如果這個(gè)Map存在芋酌,那么直接從里面獲取映射關(guān)系e
ThreadLocal.ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
// 映射關(guān)系e 存在,那么直接獲取創(chuàng)建的對(duì)象
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 程序走到這里雁佳,說(shuō)明當(dāng)前線程 這個(gè)ThreadLocal 實(shí)例對(duì)應(yīng)對(duì)象還沒(méi)有創(chuàng)建,
// 那么就進(jìn)行初始化創(chuàng)建
return setInitialValue();
}
從當(dāng)前線程存儲(chǔ)的映射關(guān)系集合
threadLocals
中糖权,查找當(dāng)前這個(gè)ThreadLocal
對(duì)象實(shí)例所對(duì)應(yīng)的對(duì)象是否存在堵腹;存在就返回,不存在就setInitialValue()
方法進(jìn)行創(chuàng)建星澳。
1.3 setInitialValue()
方法
private T setInitialValue() {
// 調(diào)用 initialValue() 方法得到初始化值
T value = initialValue();
// 先獲取當(dāng)前線程
Thread t = Thread.currentThread();
// 從當(dāng)前線程中獲取存儲(chǔ)鍵值映射關(guān)系的Map
ThreadLocal.ThreadLocalMap map = getMap(t);
if (map != null)
// 存儲(chǔ) key-value 的映射關(guān)系
map.set(this, value);
else
createMap(t, value);
return value;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
1.4 ThreadLocalMap
ThreadLocalMap
也是用一個(gè)哈希表數(shù)據(jù)結(jié)構(gòu)來(lái)儲(chǔ)存key-value
的映射關(guān)系疚顷,只不過(guò)它不是用鏈地址法來(lái)解決哈希沖突,而是用開(kāi)放地址法的 線性探測(cè)來(lái)解決哈希沖突禁偎。
關(guān)于哈希表腿堤,以及鏈地址法和開(kāi)放地址法的原理,在我的這篇文章哈希表 中有全面的介紹如暖。
1.5 小結(jié)
從圖中我們就可以知道笆檀,每個(gè)線程中都一個(gè)
threadLocals
屬性,它的類型是ThreadLocalMap
, 這個(gè)ThreadLocalMap
會(huì)記錄當(dāng)前線程所有產(chǎn)生的ThreadLocal
對(duì)象装处。
二. FastThreadLocal<V>
private static final FastThreadLocal<AtomicInteger> fastThreadLocal = new FastThreadLocal<AtomicInteger>(){
@Override
protected AtomicInteger initialValue() {
AtomicInteger result = new AtomicInteger(0);
System.out.println("創(chuàng)建 AtomicInteger("+result.hashCode()+") thread:"+Thread.currentThread().getName());
return result;
}
};
public static void main(String[] args) {
for (int index = 0; index < 2; index++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(fastThreadLocal.get().incrementAndGet()
+" thread:"+Thread.currentThread().getName());
}
}
}).start();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
運(yùn)行結(jié)果
創(chuàng)建 AtomicInteger(125165235) thread:Thread-1
創(chuàng)建 AtomicInteger(1223947416) thread:Thread-0
1 thread:Thread-1
1 thread:Thread-0
2 thread:Thread-1
2 thread:Thread-0
3 thread:Thread-1
3 thread:Thread-0
4 thread:Thread-1
5 thread:Thread-1
4 thread:Thread-0
5 thread:Thread-0
從運(yùn)行結(jié)果來(lái)看误债,FastThreadLocal<V>
和 ThreadLocal<T>
效果是一樣的,那么 FastThreadLocal<V>
的優(yōu)點(diǎn)在哪里呢妄迁?
從上面的介紹中寝蹈,我們知道 ThreadLocal<T>
通過(guò)哈希表來(lái)儲(chǔ)存數(shù)據(jù),從哈希表查找數(shù)據(jù)的過(guò)程如下:
- 根據(jù)
ThreadLocal<T>
實(shí)例對(duì)象threadLocal
的哈希值登淘,得到對(duì)應(yīng)數(shù)組下標(biāo)箫老。 - 再比較這個(gè)數(shù)組下標(biāo)存儲(chǔ)的映射關(guān)系
entry
的key
和實(shí)例對(duì)象threadLocal
是否相等,相等的話黔州,就直接返回entry
的value
值耍鬓。 - 如果不等阔籽,那么就要進(jìn)行線性探測(cè),查找下一個(gè)下標(biāo)的映射關(guān)系
entry
牲蜀,是否符合要求笆制,知道找到映射關(guān)系entry
的key
和當(dāng)前實(shí)例對(duì)象threadLocal
相等。 - 所以對(duì)于這種開(kāi)發(fā)地址法的哈希表涣达,極個(gè)別情況下在辆,查找過(guò)程可能會(huì)耗時(shí),要進(jìn)行多次線性探測(cè)度苔。
那么 FastThreadLocal<V>
就采用了空間換時(shí)間的方式加快查找速度匆篓。
-
ThreadLocal<T>
VSFastThreadLocal<V>
-
ThreadLocal<T>
有一個(gè)threadLocalHashCode
屬性,在創(chuàng)建的時(shí)候被賦值寇窑,而且是不可變的屬性鸦概,代表當(dāng)前這個(gè)ThreadLocal<T>
實(shí)例對(duì)象的哈希值,用來(lái)在哈希表ThreadLocalMap
中查找對(duì)應(yīng)的映射關(guān)系甩骏。 -
FastThreadLocal<V>
有一個(gè)index
屬性窗市,在創(chuàng)建的時(shí)候被賦值,而且是不可變的屬性饮笛,這個(gè)值就代表當(dāng)前FastThreadLocal<V>
實(shí)例對(duì)象在InternalThreadLocalMap
實(shí)例的indexedVariables
的下標(biāo)谨设,通過(guò)這個(gè)下標(biāo)得到FastThreadLocal<V>
所創(chuàng)建的當(dāng)前線程對(duì)象。
-
-
ThreadLocalMap
和InternalThreadLocalMap
-
ThreadLocalMap
是一個(gè)哈希表缎浇,用來(lái)儲(chǔ)存ThreadLocal<T>
實(shí)例對(duì)象和它所創(chuàng)建的當(dāng)前線程對(duì)象的映射關(guān)系,就可以通過(guò)ThreadLocal<T>
實(shí)例對(duì)象查找它所創(chuàng)建的當(dāng)前線程對(duì)象赴肚。 -
InternalThreadLocalMap
就是一個(gè)數(shù)組素跺,用來(lái)存儲(chǔ)FastThreadLocal<V>
實(shí)例對(duì)象所創(chuàng)建的當(dāng)前線程對(duì)象,不過(guò)存儲(chǔ)這個(gè)值的數(shù)組下標(biāo)就是FastThreadLocal<V>
實(shí)例對(duì)象的index
屬性值誉券。 -
InternalThreadLocalMap
數(shù)組下標(biāo)0
這個(gè)位置比較特殊指厌,0
下標(biāo)存儲(chǔ)當(dāng)前線程所有的FastThreadLocal<V>
對(duì)象實(shí)例,用于當(dāng)前線程銷毀時(shí)踊跟,移除當(dāng)前線程所有的FastThreadLocal<V>
對(duì)象實(shí)例所創(chuàng)建的當(dāng)前線程對(duì)象踩验。
-
需要注意的點(diǎn):
- 每個(gè)
FastThreadLocal
再創(chuàng)建的時(shí)候,index
屬性就被賦值了商玫,也就是說(shuō)這個(gè)FastThreadLocal
實(shí)例箕憾,在每個(gè)線程獲取的InternalThreadLocalMap
中的下標(biāo)是一樣的,都是index
拳昌。這就導(dǎo)致一個(gè)嚴(yán)重問(wèn)題袭异,如果
FastThreadLocal
實(shí)例較多的話,某一個(gè)線程用到了index
較大FastThreadLocal
實(shí)例的話炬藤,它必須創(chuàng)建一個(gè)很大的數(shù)組御铃,這樣才能在FastThreadLocal
實(shí)例對(duì)應(yīng)下標(biāo)index
中儲(chǔ)存FastThreadLocal
實(shí)例創(chuàng)建的對(duì)象碴里。 -
ThreadLocal
沒(méi)有這個(gè)問(wèn)題,雖然它的哈希值也是創(chuàng)建的時(shí)候就確定了上真,但是它通過(guò) 哈希的方法尋找數(shù)組下標(biāo)咬腋,那么當(dāng)前線程中ThreadLocalMap
的數(shù)組長(zhǎng)度只會(huì)和當(dāng)前線程擁有的ThreadLocal
實(shí)例有關(guān)。這個(gè)的問(wèn)題就是通過(guò)哈希查找睡互,效率有點(diǎn)影響根竿。
-
InternalThreadLocalMap
的0
下標(biāo)做了特殊處理,用來(lái)存放每個(gè)線程擁有的FastThreadLocal
實(shí)例集合湃缎,當(dāng)線程退出時(shí)犀填,清理這些FastThreadLocal
實(shí)例為當(dāng)前線程中產(chǎn)生的對(duì)象。 -
ThreadLocalMap
沒(méi)有做這方面的處理嗓违,那是因?yàn)?ThreadLocalMap
中使用WeakReference<ThreadLocal<?>>
來(lái)記錄ThreadLocal<?>
實(shí)例的九巡。