java.lang.ThreadLocal
ThreadLocal類通過線程封閉的方式解決線程安全,提到它,大家都會(huì)想到弱引用和內(nèi)存泄漏等話題,它的get/set/remove等方法,網(wǎng)上有很多關(guān)于它的話題和詳細(xì)介紹.這篇文章不會(huì)介紹這些內(nèi)容.
ThreadLocal作為key被存放到線程的ThreadLocal.ThreadLocalMap中.我們簡單看下它的set方法.
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
// this就是ThreadLocal,它作為key
map.set(this, value);
else
createMap(t, value);
}
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
// 這里的key就是ThreadLocal對象,根據(jù)它的threadLocalHashCode屬性值進(jìn)行取模計(jì)算,得到它應(yīng)該所在的下標(biāo)值是多少
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
// 如果所在的下標(biāo)已經(jīng)有值了,則根據(jù)線性探測繼續(xù)計(jì)算下一個(gè)下標(biāo)值
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
...
}
...
}
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
根據(jù)以上的分析,ThreadLocal對象作為ThreadLocal.ThreadLocalMap中的key存放在Map中.(Map的底層是基于數(shù)組)
具體是根據(jù)ThreadLocal對象的threadLocalHashCode屬性值進(jìn)行取模計(jì)算出它在數(shù)組中的下標(biāo),假如ThreadLocal的threadLocalHashCode=1253254570,數(shù)組的默認(rèn)長度是16,因此threadLocalHashCode & (16-1)=10,因此這個(gè)ThreadLocal對象應(yīng)該放在數(shù)組的第10個(gè)位置.如果第10個(gè)位置已經(jīng)有別的ThreadLocal對象霸占了,那么它就會(huì)嘗試第11個(gè)位置是否有空缺,以此類推,直到找到一個(gè)空缺位置.
開放定址法中的線性探測
舉例如下
import io.netty.util.concurrent.FastThreadLocal;
public class Address {
// 定義了兩個(gè)ThreadLocal對象
public static final ThreadLocal<String> COMPANY = new ThreadLocal<String>() {
@Override
protected String initialValue() {
return "CHINA";
}
};
public static final ThreadLocal<Integer> YEAR = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 1949;
}
};
}
Thread t1 = new Thread(() -> {
System.out.println(Address.COMPANY.get());
System.out.println(Address.YEAR.get());
try {
TimeUnit.MINUTES.sleep(15);// 只是為了讓線程不終止
} catch (InterruptedException ignored) { }
}, "thread-1");
t1.start();
Thread t2 = new Thread(() -> {
System.out.println(Address.COMPANY.get());
System.out.println(Address.YEAR.get());
try {
TimeUnit.MINUTES.sleep(15);// 只是為了讓線程不終止
} catch (InterruptedException ignored) { }
}, "thread-2");
t2.start();
根據(jù)代碼和截圖內(nèi)容,我們總結(jié)下
線程2的哈希值1253254570的ThreadLocal存在索引10位置
線程2的哈希值-1401181199的ThreadLocal存在索引1位置
線程1的哈希值1253254570的ThreadLocal存在索引10位置
線程1的哈希值-1401181199的ThreadLocal存在索引1位置
JDK的ThreadLocal是根據(jù)它的哈希值然后再取模計(jì)算出索引位置,如果沖突還要再根據(jù)開放地址法-線性探測繼續(xù)尋找下一個(gè)可用索引的位置.性能是比較低的. 那么我們看下在Netty中它是如何優(yōu)化這個(gè)功能的呢?
import io.netty.util.concurrent.FastThreadLocalThread;
FastThreadLocalThread t3 = new FastThreadLocalThread(() -> {
System.out.println(Address.FAST_COMPANY.get());
System.out.println(Address.FAST_YEAR.get());
try {
TimeUnit.MINUTES.sleep(15);
} catch (InterruptedException ignored) {
}
}, "thread-3");
t3.start();
FastThreadLocalThread t4 = new FastThreadLocalThread(() -> {
System.out.println(Address.FAST_COMPANY.get());
System.out.println(Address.FAST_YEAR.get());
try {
TimeUnit.MINUTES.sleep(15);
} catch (InterruptedException ignored) {
}
}, "thread-4");
t4.start();
這段代碼使用了Netty提供的FastThreadLocalThread線程,在它的內(nèi)部有個(gè)自己的InternalThreadLocalMap,這個(gè)Map是與FastThreadLocal搭配使用的.
FastThreadLocal內(nèi)部并不像JDK的ThreadLocal是根據(jù)哈希值與取模計(jì)算索引位置,它是在創(chuàng)建FastThreadLocal的時(shí)候就已經(jīng)確定了索引位置,在JVM中每個(gè)FastThreadLocal的索引值都是不同的.
根據(jù)索引直接存取值,時(shí)間復(fù)雜度O(1)
private final int index;
public FastThreadLocal() {
// 創(chuàng)建FastThreadLocal時(shí)它的索引值index就確定下來了
index = InternalThreadLocalMap.nextVariableIndex();
}
像Netty這樣追求性能的底層網(wǎng)絡(luò)框架,自己設(shè)計(jì)一個(gè)FastThreadLocalThread線程類,自己設(shè)計(jì)一個(gè)FastThreadLocal類,自己設(shè)計(jì)一個(gè)InternalThreadLocalMap類等.
公眾號: Netty歷險(xiǎn)記