問(wèn)題
如果一段代碼中所需要的數(shù)據(jù)必須與其他代碼共享逗载,那么就需要看這些共享數(shù)據(jù)的代碼能否在同一個(gè)線程中執(zhí)行瓷马,如果能保證叭喜,那么我們就可以把共享數(shù)據(jù)的可見(jiàn)范圍限制在同一線程之內(nèi)囚聚,這樣無(wú)須同步也能保證線程之間不出現(xiàn)數(shù)據(jù)的爭(zhēng)用問(wèn)題靖榕。
實(shí)現(xiàn)
在java中可以通過(guò)java.lang.ThreadLocal類(lèi)來(lái)實(shí)現(xiàn)線程本地存儲(chǔ)的功能。每個(gè)線程的Thread對(duì)象中都有一個(gè)ThreadLocalMap對(duì)象顽铸,這個(gè)對(duì)象存儲(chǔ)了一組以TreadLocal.threadLocalHashCode為鍵茁计,以本地線程變量為值的K-V值對(duì),每一個(gè)ThreadLocal對(duì)象都包含了一個(gè)獨(dú)一無(wú)二的threadLocalHashCode值谓松,使用這個(gè)值就可以在線程K-V值對(duì)中找到對(duì)應(yīng)的本地線程變量星压。
源碼分析
測(cè)試代碼
public class TestThreadLocal {
private static final ThreadLocal<Integer> value = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 0;
}
};
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
new Thread(new TestThread(i)).start();
}
}
static class TestThread implements Runnable {
private int index;
public TestThread(int index) {
this.index = index;
}
public void run() {
System.out.println("線程" + index + "的累加前:" + value.get());
for (int i = 0; i < 5; i++) {
value.set(value.get() + i);
}
System.out.println("線程" + index + "的累加后:" + value.get());
}
}
}
結(jié)果
從上面的代碼運(yùn)行結(jié)果可以看出,各個(gè)線程的value值是相互獨(dú)立的鬼譬,本線程的累加操作不會(huì)影響到其他線程的值娜膘。
如何實(shí)現(xiàn)呢
這是JDK1.8版本的ThreadLocal的get()方法
public T get() {
//獲取到當(dāng)前線程
Thread t = Thread.currentThread();
//以當(dāng)前線程為key拿到該線程自己的ThreadLocalMap對(duì)象
ThreadLocalMap map = getMap(t);
if (map != null) {
//根據(jù)ThreadLocal.threadLocalHashCode拿到Entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
//Entry不為空,則返回value
T result = (T)e.value;
return result;
}
}
//ThreadLocalMap 為空或者e為空优质,則通過(guò)initialValue函數(shù)獲取初始值value竣贪,
//然后用ThreadLocal的引用和value作為key和value創(chuàng)建一個(gè)新的Map
return setInitialValue();
}
//getEntry()方法。根據(jù)ThreadLocal.threadLocalHashCode拿到Entry
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);
}
//setInitialValue()方法巩螃。ThreadLocalMap 為空或者e為空演怎,則通過(guò)initialValue函數(shù)獲取初始值value忱叭,
//然后用ThreadLocal的引用和value作為key和value創(chuàng)建一個(gè)新的Map
private T setInitialValue() {
//value默認(rèn)是NULL
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
以上就是get()方法的流程:
① 獲取當(dāng)前線程
②然后根據(jù)當(dāng)前線程獲取ThreadLocalMap
③如果獲取的ThreadLocalMap不為空赐纱,則在ThreadLocalMap中以ThreadLocal.threadLocalHashCode為key拿到Entry镀迂,最后獲取到value粗蔚。如果ThreadLocalMap為空或者e為空殖氏,則轉(zhuǎn)為④
④通過(guò)initialValue函數(shù)獲取初始值value奔穿,然后用ThreadLocal的引用和value作為key和value創(chuàng)建一個(gè)新的Map
接下來(lái)看下ThreadLocal的set()方法
public void set(T value) {
//獲取到當(dāng)前線程
Thread t = Thread.currentThread();
//拿到當(dāng)前線程的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
//不為空独柑,調(diào)用ThreadLocalMap 的set()方法設(shè)置值盈厘,代碼在下面
map.set(this, value);
else
//為空春缕,用ThreadLocal的引用和value作為key和value創(chuàng)建一個(gè)新的Map
createMap(t, value);
}
//ThreadLocalMap 的set()方法
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) {
//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();
}
參考
周志明:《深入理解Java虛擬機(jī)》