一贾节、簡(jiǎn)介
首先我們需要知道Thread.currentThread()獲取當(dāng)前線程對(duì)象魄咕,同一個(gè)線程每次獲取的都是同一個(gè)Thread對(duì)象吟税。
ThreadLocal主要是用來將數(shù)據(jù)與線程綁定犬辰。
ThreadLocal其實(shí)主要思想就是將數(shù)據(jù)保存在當(dāng)前線程對(duì)象,然后同ThreadLocal對(duì)象去操作保存的對(duì)象填物。主要方法包括設(shè)置值全庸,取值,移除等操作融痛。
二壶笼、類的結(jié)構(gòu)
還有兩個(gè)內(nèi)部類的結(jié)構(gòu)
三、主要方法
initialValue()
protected T initialValue() {
return null;
}
可以看到這是一個(gè)鉤子方法雁刷,所以我們可以重寫該方法覆劈,獲得更好的實(shí)現(xiàn)效果。
set()
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
當(dāng)前線程已經(jīng)綁定了ThreadLocalMap,則直接設(shè)置值調(diào)用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) {
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();
}
int i = key.threadLocalHashCode & (len-1);
這里用到了神奇的數(shù)字0x61c88647沛励,用它可以將值更優(yōu)的放到2的冪大小的數(shù)組中责语,舉個(gè)栗子可以將打印內(nèi)容作為數(shù)組位置。
public static void main(String[] args) {
int t = 0x61c88647;
AtomicInteger atomicInteger = new AtomicInteger();
int size = 1<<4;
for (int i = 0; i < size; i++) {
atomicInteger.getAndAdd(t);
System.out.println(atomicInteger.get()&(size-1));
}
}
threadLocalHashCode是final修飾的,所以直接使用該值不會(huì)修改目派,一般同一個(gè)threadLocal對(duì)象進(jìn)來獲取的是同一個(gè)位置坤候。
ThreadLocalMap是保存在Thread中。
引用關(guān)系如下圖所示
set將key為ThreadLocal,value 為我們自定的對(duì)象的map保存到當(dāng)前Thread對(duì)象中企蹭。
當(dāng)map不為空的時(shí)候?qū)alue與當(dāng)前線程綁定,創(chuàng)建ThreadLocal.ThreadLocalMap白筹,然后將value與當(dāng)前線程綁定,其實(shí)只有一句代碼
t.threadLocals = new ThreadLocalMap(this, firstValue);
get()
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();
}
getMap(t)是獲取當(dāng)前線程綁定的ThreadLocal.ThreadLocalMap對(duì)象,這個(gè)對(duì)象保存在Thread對(duì)象里的threadLocals屬性谅摄,內(nèi)容值也就是ThreadLocal的內(nèi)部類ThreadLocalMap的實(shí)例徒河,即我們?cè)O(shè)置過的key是當(dāng)前ThreadLocal對(duì)象,value為我們自定義的對(duì)象送漠。
如果為空則返回的是調(diào)用setInitialValue獲取的值顽照,這里就跟方法initialValue有關(guān)聯(lián)了。我們可以看一下實(shí)現(xiàn)闽寡。
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
根據(jù)此我們可以重寫initialValue()方法代兵,放入我們要保存的value的默認(rèn)值。
四爷狈、項(xiàng)目中使用
情景:老項(xiàng)目因?yàn)橐恍┰蚝芏嘟涌谝砑訁?shù)植影,如果接口里面都添加參數(shù),調(diào)用方法添加參數(shù)淆院,但是不想影響結(jié)構(gòu)何乎。那么可以用ThreadLocal來設(shè)置和獲取參數(shù)的方式,防止對(duì)已有內(nèi)容造成影響土辩,首先得弄清楚支救,你對(duì)數(shù)據(jù)的處理是不是在同一個(gè)線程中。
對(duì)于接口添加參數(shù)的做法就是在攔截器里獲取參數(shù)拷淘,添加到ThreadLocal中各墨,后面哪里使用哪里取出來就好了。如果后面代碼多線程的話不要直接使用启涯。而存取可以用工具類的方式贬堵。比如:
public final class TestUtil{
private static final String KEY= "參數(shù)";
private static final ThreadLocal<Map<String, String>> sessionStore = new ThreadLocal<Map<String, String>>() {
@Override
protected Map<String, String> initialValue() {
return new HashMap<>();
}
};
public static void add(String content) {
Map<String, String> sessionMap = sessionStore.get();
sessionMap.put(KEY, content);
}
public static Locale get(){
return sessionStore.get().get(KEY);
}
public static void remove() {
sessionStore.remove();
}
}
這里用了static修飾ThreadLocal,如果對(duì)象很大结洼,線程時(shí)間很長(zhǎng)的情況下建議不要用static修飾ThreadLocal黎做,因?yàn)槟J(rèn)情況下因?yàn)橛昧巳跻玫姆绞剑簿褪钱?dāng)ThreadLocal只有弱引用沒有其他引用的時(shí)候松忍,該對(duì)象將自動(dòng)被GC蒸殿,在調(diào)用調(diào)用ThreadLocal其他方法的時(shí)候可以清理value對(duì)象。根據(jù)此特性鸣峭,在消耗比較大的情況下宏所,可以做一定優(yōu)化。避免內(nèi)存泄露摊溶。
五爬骤、引申
其實(shí)里面用的一些方式也可以在其他項(xiàng)目中使用
- 弱引用的使用
- 神奇的數(shù)字0x61c88647的使用
- 鉤子方法的使用
- ThreadLocal本身的使用
如果文中有錯(cuò)誤的地方,歡迎指出莫换,以免造成誤導(dǎo)霞玄。