在平常開發(fā)的時(shí)候,經(jīng)常使用到線程本地變量,這種類型的變量會(huì)在每個(gè)線程中都有一份,互相不會(huì)產(chǎn)生影響,這樣來解決多線程并發(fā)問題。
那么是如何實(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來查找對(duì)應(yīng)的所創(chuàng)建的對(duì)象AtomicInteger,相當(dāng)于key->value的鍵值映射關(guān)系。- 而每個(gè)線程可以有多個(gè)
ThreadLocal對(duì)象實(shí)例,即多個(gè)key。- 那么我們可以斷定,每個(gè)線程肯定有一個(gè)集合對(duì)象來存儲(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;
}
}
// 程序走到這里,說明當(dāng)前線程 這個(gè)ThreadLocal 實(shí)例對(duì)應(yīng)對(duì)象還沒有創(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)來儲(chǔ)存key-value 的映射關(guān)系,只不過它不是用鏈地址法來解決哈希沖突,而是用開放地址法的 線性探測來解決哈希沖突。
關(guān)于哈希表,以及鏈地址法和開放地址法的原理,在我的這篇文章哈希表 中有全面的介紹。
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é)果來看,FastThreadLocal<V> 和 ThreadLocal<T> 效果是一樣的,那么 FastThreadLocal<V> 的優(yōu)點(diǎn)在哪里呢?
從上面的介紹中,我們知道 ThreadLocal<T> 通過哈希表來儲(chǔ)存數(shù)據(jù),從哈希表查找數(shù)據(jù)的過程如下:
- 根據(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)行線性探測,查找下一個(gè)下標(biāo)的映射關(guān)系
entry,是否符合要求,知道找到映射關(guān)系entry的key和當(dāng)前實(shí)例對(duì)象threadLocal相等。 - 所以對(duì)于這種開發(fā)地址法的哈希表,極個(gè)別情況下,查找過程可能會(huì)耗時(shí),要進(jìn)行多次線性探測。
那么 FastThreadLocal<V> 就采用了空間換時(shí)間的方式加快查找速度。
-
ThreadLocal<T>VSFastThreadLocal<V>-
ThreadLocal<T>有一個(gè)threadLocalHashCode屬性,在創(chuàng)建的時(shí)候被賦值,而且是不可變的屬性,代表當(dāng)前這個(gè)ThreadLocal<T>實(shí)例對(duì)象的哈希值,用來在哈希表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),通過這個(gè)下標(biāo)得到FastThreadLocal<V>所創(chuàng)建的當(dāng)前線程對(duì)象。
-
-
ThreadLocalMap和InternalThreadLocalMap-
ThreadLocalMap是一個(gè)哈希表,用來儲(chǔ)存ThreadLocal<T>實(shí)例對(duì)象和它所創(chuàng)建的當(dāng)前線程對(duì)象的映射關(guān)系,就可以通過ThreadLocal<T>實(shí)例對(duì)象查找它所創(chuàng)建的當(dāng)前線程對(duì)象。 -
InternalThreadLocalMap就是一個(gè)數(shù)組,用來存儲(chǔ)FastThreadLocal<V>實(shí)例對(duì)象所創(chuàng)建的當(dāng)前線程對(duì)象,不過存儲(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屬性就被賦值了,也就是說這個(gè)FastThreadLocal實(shí)例,在每個(gè)線程獲取的InternalThreadLocalMap中的下標(biāo)是一樣的,都是index。這就導(dǎo)致一個(gè)嚴(yá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沒有這個(gè)問題,雖然它的哈希值也是創(chuàng)建的時(shí)候就確定了,但是它通過 哈希的方法尋找數(shù)組下標(biāo),那么當(dāng)前線程中ThreadLocalMap的數(shù)組長度只會(huì)和當(dāng)前線程擁有的ThreadLocal實(shí)例有關(guān)。這個(gè)的問題就是通過哈希查找,效率有點(diǎn)影響。
-
InternalThreadLocalMap的0下標(biāo)做了特殊處理,用來存放每個(gè)線程擁有的FastThreadLocal實(shí)例集合,當(dāng)線程退出時(shí),清理這些FastThreadLocal實(shí)例為當(dāng)前線程中產(chǎn)生的對(duì)象。 -
ThreadLocalMap沒有做這方面的處理,那是因?yàn)?ThreadLocalMap中使用WeakReference<ThreadLocal<?>>來記錄ThreadLocal<?>實(shí)例的。