下面我就以面試問答的形式學(xué)習(xí)我們的——ThreadLocal類(源碼分析基于JDK8)
問答內(nèi)容
1婴谱、問:ThreadLocal了解嗎思犁?您能給我說說他的主要用途嗎车份?
答:從JAVA官方對(duì)ThreadLocal類的說明定義(定義在示例代碼中):ThreadLocal類用來提供線程內(nèi)部的局部變量驻子。這種變量在多線程環(huán)境下訪問(通過get和set方法訪問)時(shí)能保證各個(gè)線程的變量相對(duì)獨(dú)立于其他線程內(nèi)的變量。ThreadLocal實(shí)例通常來說都是private static類型的估灿,用于關(guān)聯(lián)線程和線程上下文崇呵。
我們可以得知ThreadLocal的作用是:ThreadLocal的作用是提供線程內(nèi)的局部變量,不同的線程之間不會(huì)相互干擾馅袁,這種變量在線程的生命周期內(nèi)起作用域慷,減少同一個(gè)線程內(nèi)多個(gè)函數(shù)或組件之間一些公共變量的傳遞的復(fù)雜度。
上述可以概述為:ThreadLocal提供線程內(nèi)部的局部變量汗销,在本線程內(nèi)隨時(shí)隨地可取犹褒,隔離其他線程。
2.ThreadLocal實(shí)現(xiàn)原理是什么大溜,它是怎么樣做到局部變量不同的線程之間不會(huì)相互干擾的?
JDK8 ThreadLocal的設(shè)計(jì)是:每個(gè)Thread維護(hù)一個(gè)ThreadLocalMap哈希表估脆,這個(gè)哈希表的key是ThreadLocal實(shí)例本身钦奋,value才是真正要存儲(chǔ)的值Object。
這樣設(shè)計(jì)有如下幾點(diǎn)優(yōu)勢(shì):
- 1疙赠、這樣設(shè)計(jì)之后每個(gè)Map存儲(chǔ)的Entry數(shù)量就會(huì)變小付材,因?yàn)橹暗拇鎯?chǔ)數(shù)量由Thread的數(shù)量決定,現(xiàn)在是由ThreadLocal的數(shù)量決定圃阳。
- 2厌衔、當(dāng)Thread銷毀之后,對(duì)應(yīng)的ThreadLocalMap也會(huì)隨之銷毀捍岳,能減少內(nèi)存的使用富寿。
3、您能說說ThreadLocal常用操作的底層實(shí)現(xiàn)原理嗎锣夹?如存儲(chǔ)set(T value)页徐,獲取get(),刪除remove()等操作银萍。
調(diào)用get()操作獲取ThreadLocal中對(duì)應(yīng)當(dāng)前線程存儲(chǔ)的值時(shí)变勇,進(jìn)行了如下操作:
- 1、獲取當(dāng)前線程Thread對(duì)象贴唇,進(jìn)而獲取此線程對(duì)象中維護(hù)的ThreadLocalMap對(duì)象搀绣。
- 2、判斷當(dāng)前的ThreadLocalMap是否存在:
如果存在戳气,則以當(dāng)前的ThreadLocal 為 key链患,調(diào)用ThreadLocalMap中的getEntry方法獲取對(duì)應(yīng)的存儲(chǔ)實(shí)體 e。找到對(duì)應(yīng)的存儲(chǔ)實(shí)體 e瓶您,獲取存儲(chǔ)實(shí)體 e 對(duì)應(yīng)的 value值锣险,即為我們想要的當(dāng)前線程對(duì)應(yīng)此ThreadLocal的值蹄皱,返回結(jié)果值。
如果不存在芯肤,則證明此線程沒有維護(hù)的ThreadLocalMap對(duì)象巷折,調(diào)用setInitialValue方法進(jìn)行初始化。返回setInitialValue初始化的值崖咨。
setInitialValue方法的操作如下:
- 1锻拘、調(diào)用initialValue獲取初始化的值。
- 2击蹲、獲取當(dāng)前線程Thread對(duì)象署拟,進(jìn)而獲取此線程對(duì)象中維護(hù)的ThreadLocalMap對(duì)象。
- 3歌豺、判斷當(dāng)前的ThreadLocalMap是否存在:
如果存在推穷,則調(diào)用map.set設(shè)置此實(shí)體entry。
如果不存在类咧,則調(diào)用createMap進(jìn)行ThreadLocalMap對(duì)象的初始化馒铃,并將此實(shí)體entry作為第一個(gè)值存放至ThreadLocalMap中。
/**
* 返回當(dāng)前線程對(duì)應(yīng)的ThreadLocal的初始值
* 此方法的第一次調(diào)用發(fā)生在痕惋,當(dāng)線程通過{@link #get}方法訪問此線程的ThreadLocal值時(shí)
* 除非線程先調(diào)用了 {@link #set}方法区宇,在這種情況下,
* {@code initialValue} 才不會(huì)被這個(gè)線程調(diào)用值戳。
* 通常情況下议谷,每個(gè)線程最多調(diào)用一次這個(gè)方法,
* 但也可能再次調(diào)用堕虹,發(fā)生在調(diào)用{@link #remove}方法后卧晓,
* 緊接著調(diào)用{@link #get}方法。
*
* <p>這個(gè)方法僅僅簡(jiǎn)單的返回null {@code null};
* 如果程序員想ThreadLocal線程局部變量有一個(gè)除null以外的初始值赴捞,
* 必須通過子類繼承{@code ThreadLocal} 的方式去重寫此方法
* 通常, 可以通過匿名內(nèi)部類的方式實(shí)現(xiàn)
*
* @return 當(dāng)前ThreadLocal的初始值
*/
protected T initialValue() {
return null;
}
/**
* 創(chuàng)建一個(gè)ThreadLocal
* @see #withInitial(java.util.function.Supplier)
*/
public ThreadLocal() {
}
/**
* 返回當(dāng)前線程中保存ThreadLocal的值
* 如果當(dāng)前線程沒有此ThreadLocal變量禀崖,
* 則它會(huì)通過調(diào)用{@link #initialValue} 方法進(jìn)行初始化值
*
* @return 返回當(dāng)前線程對(duì)應(yīng)此ThreadLocal的值
*/
public T get() {
// 獲取當(dāng)前線程對(duì)象
Thread t = Thread.currentThread();
// 獲取此線程對(duì)象中維護(hù)的ThreadLocalMap對(duì)象
ThreadLocalMap map = getMap(t);
// 如果此map存在
if (map != null) {
// 以當(dāng)前的ThreadLocal 為 key,調(diào)用getEntry獲取對(duì)應(yīng)的存儲(chǔ)實(shí)體e
ThreadLocalMap.Entry e = map.getEntry(this);
// 找到對(duì)應(yīng)的存儲(chǔ)實(shí)體 e
if (e != null) {
@SuppressWarnings("unchecked")
// 獲取存儲(chǔ)實(shí)體 e 對(duì)應(yīng)的 value值
// 即為我們想要的當(dāng)前線程對(duì)應(yīng)此ThreadLocal的值
T result = (T)e.value;
return result;
}
}
// 如果map不存在螟炫,則證明此線程沒有維護(hù)的ThreadLocalMap對(duì)象
// 調(diào)用setInitialValue進(jìn)行初始化
return setInitialValue();
}
/**
* set的變樣實(shí)現(xiàn)波附,用于初始化值initialValue,
* 用于代替防止用戶重寫set()方法
*
* @return the initial value 初始化后的值
*/
private T setInitialValue() {
// 調(diào)用initialValue獲取初始化的值
T value = initialValue();
// 獲取當(dāng)前線程對(duì)象
Thread t = Thread.currentThread();
// 獲取此線程對(duì)象中維護(hù)的ThreadLocalMap對(duì)象
ThreadLocalMap map = getMap(t);
// 如果此map存在
if (map != null)
// 存在則調(diào)用map.set設(shè)置此實(shí)體entry
map.set(this, value);
else
// 1)當(dāng)前線程Thread 不存在ThreadLocalMap對(duì)象
// 2)則調(diào)用createMap進(jìn)行ThreadLocalMap對(duì)象的初始化
// 3)并將此實(shí)體entry作為第一個(gè)值存放至ThreadLocalMap中
createMap(t, value);
// 返回設(shè)置的值value
return value;
}
/**
* 獲取當(dāng)前線程Thread對(duì)應(yīng)維護(hù)的ThreadLocalMap
*
* @param t the current thread 當(dāng)前線程
* @return the map 對(duì)應(yīng)維護(hù)的ThreadLocalMap
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
調(diào)用set(T value)操作設(shè)置ThreadLocal中對(duì)應(yīng)當(dāng)前線程要存儲(chǔ)的值時(shí)昼钻,進(jìn)行了如下操作:
- 1掸屡、獲取當(dāng)前線程Thread對(duì)象,進(jìn)而獲取此線程對(duì)象中維護(hù)的ThreadLocalMap對(duì)象然评。
- 2仅财、判斷當(dāng)前的ThreadLocalMap是否存在:
如果存在,則調(diào)用map.set設(shè)置此實(shí)體entry碗淌。
如果不存在盏求,則調(diào)用createMap進(jìn)行ThreadLocalMap對(duì)象的初始化抖锥,并將此實(shí)體entry作為第一個(gè)值存放至ThreadLocalMap中。
/**
* 設(shè)置當(dāng)前線程對(duì)應(yīng)的ThreadLocal的值
* 大多數(shù)子類都不需要重寫此方法碎罚,
* 只需要重寫 {@link #initialValue}方法代替設(shè)置當(dāng)前線程對(duì)應(yīng)的ThreadLocal的值
*
* @param value 將要保存在當(dāng)前線程對(duì)應(yīng)的ThreadLocal的值
*
*/
public void set(T value) {
// 獲取當(dāng)前線程對(duì)象
Thread t = Thread.currentThread();
// 獲取此線程對(duì)象中維護(hù)的ThreadLocalMap對(duì)象
ThreadLocalMap map = getMap(t);
// 如果此map存在
if (map != null)
// 存在則調(diào)用map.set設(shè)置此實(shí)體entry
map.set(this, value);
else
// 1)當(dāng)前線程Thread 不存在ThreadLocalMap對(duì)象
// 2)則調(diào)用createMap進(jìn)行ThreadLocalMap對(duì)象的初始化
// 3)并將此實(shí)體entry作為第一個(gè)值存放至ThreadLocalMap中
createMap(t, value);
}
/**
* 為當(dāng)前線程Thread 創(chuàng)建對(duì)應(yīng)維護(hù)的ThreadLocalMap.
*
* @param t the current thread 當(dāng)前線程
* @param firstValue 第一個(gè)要存放的ThreadLocal變量值
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
調(diào)用remove()操作刪除ThreadLocal中對(duì)應(yīng)當(dāng)前線程已存儲(chǔ)的值時(shí)磅废,進(jìn)行了如下操作:
- 1、獲取當(dāng)前線程Thread對(duì)象荆烈,進(jìn)而獲取此線程對(duì)象中維護(hù)的ThreadLocalMap對(duì)象拯勉。
- 2、判斷當(dāng)前的ThreadLocalMap是否存在憔购, 如果存在宫峦,則調(diào)用map.remove,以當(dāng)前ThreadLocal為key刪除對(duì)應(yīng)的實(shí)體entry玫鸟。
/**
* 刪除當(dāng)前線程中保存的ThreadLocal對(duì)應(yīng)的實(shí)體entry
* 如果此ThreadLocal變量在當(dāng)前線程中調(diào)用 {@linkplain #get read}方法
* 則會(huì)通過調(diào)用{@link #initialValue}進(jìn)行再次初始化导绷,
* 除非此值value是通過當(dāng)前線程內(nèi)置調(diào)用 {@linkplain #set set}設(shè)置的
* 這可能會(huì)導(dǎo)致在當(dāng)前線程中多次調(diào)用{@code initialValue}方法
*
* @since 1.5
*/
public void remove() {
// 獲取當(dāng)前線程對(duì)象中維護(hù)的ThreadLocalMap對(duì)象
ThreadLocalMap m = getMap(Thread.currentThread());
// 如果此map存在
if (m != null)
// 存在則調(diào)用map.remove
// 以當(dāng)前ThreadLocal為key刪除對(duì)應(yīng)的實(shí)體entry
m.remove(this);
}
4、對(duì)ThreadLocal的常用操作實(shí)際是對(duì)線程Thread中的ThreadLocalMap進(jìn)行操作屎飘,核心是ThreadLocalMap這個(gè)哈希表妥曲,你能談?wù)凾hreadLocalMap的內(nèi)部底層實(shí)現(xiàn)嗎?
ThreadLocalMap的底層實(shí)現(xiàn)是一個(gè)定制的自定義HashMap哈希表,核心組成元素有:
- 1枚碗、Entry[] table;:底層哈希表 table, 必要時(shí)需要進(jìn)行擴(kuò)容逾一,底層哈希表 table.length 長(zhǎng)度必須是2的n次方铸本。
- 2肮雨、int size;:實(shí)際存儲(chǔ)鍵值對(duì)元素個(gè)數(shù) entries
- 3、int threshold;:下一次擴(kuò)容時(shí)的閾值箱玷,閾值 threshold = 底層哈希表table的長(zhǎng)度 len * 2 / 3怨规。當(dāng)size >= threshold時(shí),遍歷table并刪除key為null的元素锡足,如果刪除后size >= threshold*3/4時(shí)波丰,需要對(duì)table進(jìn)行擴(kuò)容(詳情請(qǐng)查看set(ThreadLocal<?> key, Object value)方法說明)。
其中Entry[] table;哈希表存儲(chǔ)的核心元素是Entry舶得,Entry包含:
- 1掰烟、ThreadLocal<?> k;:當(dāng)前存儲(chǔ)的ThreadLocal實(shí)例對(duì)象
- 2沐批、Object value;:當(dāng)前 ThreadLocal 對(duì)應(yīng)儲(chǔ)存的值value
需要注意的是纫骑,此Entry繼承了弱引用 WeakReference,所以在使用ThreadLocalMap時(shí)九孩,發(fā)現(xiàn)key == null先馆,則意味著此key ThreadLocal不在被引用,需要將其從ThreadLocalMap哈希表中移除躺彬。
示例代碼:
/**
* ThreadLocalMap 是一個(gè)定制的自定義 hashMap 哈希表煤墙,只適合用于維護(hù)
* 線程對(duì)應(yīng)ThreadLocal的值. 此類的方法沒有在ThreadLocal 類外部暴露梅惯,
* 此類是私有的,允許在 Thread 類中以字段的形式聲明 仿野,
* 以助于處理存儲(chǔ)量大铣减,生命周期長(zhǎng)的使用用途,
* 此類定制的哈希表實(shí)體鍵值對(duì)使用弱引用WeakReferences 作為key设预,
* 但是, 一旦引用不在被使用徙歼,
* 只有當(dāng)哈希表中的空間被耗盡時(shí),對(duì)應(yīng)不再使用的鍵值對(duì)實(shí)體才會(huì)確保被 移除回收鳖枕。
*/
static class ThreadLocalMap {
/**
* 實(shí)體entries在此hash map中是繼承弱引用 WeakReference,
* 使用ThreadLocal 作為 key 鍵. 請(qǐng)注意魄梯,當(dāng)key為null(i.e. entry.get()
* == null) 意味著此key不再被引用,此時(shí)實(shí)體entry 會(huì)從哈希表中刪除。
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** 當(dāng)前 ThreadLocal 對(duì)應(yīng)儲(chǔ)存的值value. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
/**
* 初始容量大小 16 -- 必須是2的n次方.
*/
private static final int INITIAL_CAPACITY = 16;
/**
* 底層哈希表 table, 必要時(shí)需要進(jìn)行擴(kuò)容.
* 底層哈希表 table.length 長(zhǎng)度必須是2的n次方.
*/
private Entry[] table;
/**
* 實(shí)際存儲(chǔ)鍵值對(duì)元素個(gè)數(shù) entries.
*/
private int size = 0;
/**
* 下一次擴(kuò)容時(shí)的閾值
*/
private int threshold; // 默認(rèn)為 0
/**
* 設(shè)置觸發(fā)擴(kuò)容時(shí)的閾值 threshold
* 閾值 threshold = 底層哈希表table的長(zhǎng)度 len * 2 / 3
*/
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
/**
* 獲取該位置i對(duì)應(yīng)的下一個(gè)位置index
*/
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
/**
* 獲取該位置i對(duì)應(yīng)的上一個(gè)位置index
*/
private static int prevIndex(int i, int len) {
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
}
ThreadLocalMap的構(gòu)造方法是延遲加載的宾符,也就是說酿秸,只有當(dāng)線程需要存儲(chǔ)對(duì)應(yīng)的ThreadLocal的值時(shí),才初始化創(chuàng)建一次(僅初始化一次)魏烫。
初始化步驟如下:
- 1初始化底層數(shù)組table的初始容量為 16辣苏。
- 2獲取ThreadLocal中的threadLocalHashCode,通過threadLocalHashCode & (INITIAL_CAPACITY - 1)哄褒,即ThreadLocal 的 hash 值 threadLocalHashCode % 哈希表的長(zhǎng)度 length 的方式計(jì)算該實(shí)體的存儲(chǔ)位置稀蟋。
- 3存儲(chǔ)當(dāng)前的實(shí)體,key 為 : 當(dāng)前ThreadLocal value:真正要存儲(chǔ)的值
- 4設(shè)置當(dāng)前實(shí)際存儲(chǔ)元素個(gè)數(shù) size 為 1
- 5設(shè)置閾值setThreshold(INITIAL_CAPACITY)呐赡,為初始化容量 16 的 2/3退客。
示例代碼:
/**
* 用于創(chuàng)建一個(gè)新的hash map包含 (firstKey, firstValue).
* ThreadLocalMaps 構(gòu)造方法是延遲加載的,所以我們只會(huì)在至少有一個(gè)
* 實(shí)體entry存放時(shí),才初始化創(chuàng)建一次(僅初始化一次)链嘀。
*/
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
// 初始化 table 初始容量為 16
table = new Entry[INITIAL_CAPACITY];
// 計(jì)算當(dāng)前entry的存儲(chǔ)位置
// 存儲(chǔ)位置計(jì)算等價(jià)于:
// ThreadLocal 的 hash 值 threadLocalHashCode % 哈希表的長(zhǎng)度 length
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
// 存儲(chǔ)當(dāng)前的實(shí)體萌狂,key 為 : 當(dāng)前ThreadLocal value:真正要存儲(chǔ)的值
table[i] = new Entry(firstKey, firstValue);
// 設(shè)置當(dāng)前實(shí)際存儲(chǔ)元素個(gè)數(shù) size 為 1
size = 1;
// 設(shè)置閾值,為初始化容量 16 的 2/3怀泊。
setThreshold(INITIAL_CAPACITY);
}
ThreadLocal的get()操作實(shí)際是調(diào)用ThreadLocalMap的getEntry(ThreadLocal<?> key)方法,此方法快速適用于獲取某一存在key的實(shí)體 entry茫藏,否則,應(yīng)該調(diào)用getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e)方法獲取霹琼,這樣做是為了最大限制地提高直接命中的性能务傲,該方法進(jìn)行了如下操作:
- 1、計(jì)算要獲取的entry的存儲(chǔ)位置枣申,存儲(chǔ)位置計(jì)算等價(jià)于:ThreadLocal的 hash 值 threadLocalHashCode % 哈希表的長(zhǎng)度 length售葡。
- 2、根據(jù)計(jì)算的存儲(chǔ)位置糯而,獲取到對(duì)應(yīng)的實(shí)體 Entry天通。判斷對(duì)應(yīng)實(shí)體Entry是否存在 并且 key是否相等:
存在對(duì)應(yīng)實(shí)體Entry并且對(duì)應(yīng)key相等,即同一ThreadLocal熄驼,返回對(duì)應(yīng)的實(shí)體Entry像寒。
不存在對(duì)應(yīng)實(shí)體Entry 或者 key不相等烘豹,則通過調(diào)用getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e)方法繼續(xù)查找。
getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e)方法操作如下:
- 1.獲取底層哈希表數(shù)組table,循環(huán)遍歷對(duì)應(yīng)要查找的實(shí)體Entry所關(guān)聯(lián)的位置。
- 2.獲取當(dāng)前遍歷的entry 的 key ThreadLocal很洋,比較key是否一致原押,一致則返回些阅。
- 3.如果key不一致 并且 key 為 null,則證明引用已經(jīng)不存在,這是因?yàn)镋ntry繼承的是WeakReference,這是弱引用帶來的坑轴或。調(diào)用expungeStaleEntry(int staleSlot)方法刪除過期的實(shí)體Entry(此方法不單獨(dú)解釋,請(qǐng)查看示例代碼仰禀,有詳細(xì)注釋說明)照雁。
- key不一致 ,key也不為空答恶,則遍歷下一個(gè)位置饺蚊,繼續(xù)查找。
- 5.遍歷完畢悬嗓,仍然找不到則返回null污呼。
示例代碼:
/**
* 根據(jù)key 獲取對(duì)應(yīng)的實(shí)體 entry. 此方法快速適用于獲取某一存在key的
* 實(shí)體 entry,否則包竹,應(yīng)該調(diào)用getEntryAfterMiss方法獲取燕酷,這樣做是為
* 了最大限制地提高直接命中的性能
*
* @param key 當(dāng)前thread local 對(duì)象
* @return the entry 對(duì)應(yīng)key的 實(shí)體entry, 如果不存在,則返回null
*/
private Entry getEntry(ThreadLocal<?> key) {
// 計(jì)算要獲取的entry的存儲(chǔ)位置
// 存儲(chǔ)位置計(jì)算等價(jià)于:
// ThreadLocal 的 hash 值 threadLocalHashCode % 哈希表
的長(zhǎng)度 length
int i = key.threadLocalHashCode & (table.length - 1);
// 獲取到對(duì)應(yīng)的實(shí)體 Entry
Entry e = table[i];
// 存在對(duì)應(yīng)實(shí)體并且對(duì)應(yīng)key相等映企,即同一ThreadLocal
if (e != null && e.get() == key)
// 返回對(duì)應(yīng)的實(shí)體Entry
return e;
else
// 不存在 或 key不一致悟狱,則通過調(diào)用getEntryAfterMiss繼續(xù)查找
return getEntryAfterMiss(key, i, e);
}
/**
* 當(dāng)根據(jù)key找不到對(duì)應(yīng)的實(shí)體entry 時(shí)静浴,調(diào)用此方法堰氓。
* 直接定位到對(duì)應(yīng)的哈希表位置
*
* @param key 當(dāng)前thread local 對(duì)象
* @param i 此對(duì)象在哈希表 table中的存儲(chǔ)位置 index
* @param e the entry 實(shí)體對(duì)象
* @return the entry 對(duì)應(yīng)key的 實(shí)體entry, 如果不存在,則返回null
*/
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
// 循環(huán)遍歷當(dāng)前位置的所有實(shí)體entry
while (e != null) {
// 獲取當(dāng)前entry 的 key ThreadLocal
ThreadLocal<?> k = e.get();
// 比較key是否一致苹享,一致則返回
if (k == key)
return e;
// 找到對(duì)應(yīng)的entry 双絮,但其key 為 null,則證明引用已經(jīng)不存在
// 這是因?yàn)镋ntry繼承的是WeakReference得问,這是弱引用帶來的坑
if (k == null)
// 刪除過期(stale)的entry
expungeStaleEntry(i);
else
// key不一致 囤攀,key也不為空,則遍歷下一個(gè)位置宫纬,繼續(xù)查找
i = nextIndex(i, len);
// 獲取下一個(gè)位置的實(shí)體 entry
e = tab[i];
}
// 遍歷完畢焚挠,找不到則返回null
return null;
}
/**
* 刪除對(duì)應(yīng)位置的過期實(shí)體,并刪除此位置后對(duì)應(yīng)相關(guān)聯(lián)位置key = null的實(shí)體
*
* @param staleSlot 已知的key = null 的對(duì)應(yīng)的位置索引
* @return 對(duì)應(yīng)過期實(shí)體位置索引的下一個(gè)key = null的位置
* (所有的對(duì)應(yīng)位置都會(huì)被檢查)
*/
private int expungeStaleEntry(int staleSlot) {
// 獲取對(duì)應(yīng)的底層哈希表 table
Entry[] tab = table;
// 獲取哈希表長(zhǎng)度
int len = tab.length;
// 擦除這個(gè)位置上的臟數(shù)據(jù)
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// 直到我們找到 Entry e = null漓骚,才執(zhí)行rehash操作
// 就是遍歷完該位置的所有關(guān)聯(lián)位置的實(shí)體
Entry e;
int i;
// 查找該位置對(duì)應(yīng)所有關(guān)聯(lián)位置的過期實(shí)體蝌衔,進(jìn)行擦除操作
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
// 我們必須一直遍歷直到最后
// 因?yàn)檫€可能存在多個(gè)過期的實(shí)體
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
/**
* 刪除所有過期的實(shí)體
*/
private void expungeStaleEntries() {
Entry[] tab = table;
int len = tab.length;
for (int j = 0; j < len; j++) {
Entry e = tab[j];
if (e != null && e.get() == null)
expungeStaleEntry(j);
}
}
ThreadLocal的set(T value)操作實(shí)際是調(diào)用ThreadLocalMap的set(ThreadLocal<?> key, Object value)方法榛泛,該方法進(jìn)行了如下操作:
- 1.獲取對(duì)應(yīng)的底層哈希表table,計(jì)算對(duì)應(yīng)threalocal的存儲(chǔ)位置噩斟。
- 2.循環(huán)遍歷table對(duì)應(yīng)該位置的實(shí)體曹锨,查找對(duì)應(yīng)的threadLocal。
- 3.獲取當(dāng)前位置的threadLocal剃允,如果key threadLocal一致沛简,則證明找到對(duì)應(yīng)的threadLocal,將新值賦值給找到的當(dāng)前實(shí)體Entry的value中斥废,結(jié)束椒楣。
- 4.如果當(dāng)前位置的key threadLocal不一致,并且key threadLocal為null牡肉,則調(diào)用replaceStaleEntry(ThreadLocal<?> key, Object value,int staleSlot)方法(此方法不單獨(dú)解釋撒顿,請(qǐng)查看示例代碼,有詳細(xì)注釋說明)荚板,替換該位置key == null 的實(shí)體為當(dāng)前要設(shè)置的實(shí)體凤壁,結(jié)束。
- 5.如果當(dāng)前位置的key threadLocal不一致跪另,并且key threadLocal不為null拧抖,則創(chuàng)建新的實(shí)體,并存放至當(dāng)前位置 i tab[i] = new Entry(key, value);免绿,實(shí)際存儲(chǔ)鍵值對(duì)元素個(gè)數(shù)size + 1唧席,由于弱引用帶來了這個(gè)問題,所以要調(diào)用cleanSomeSlots(int i, int n)方法清除無用數(shù)據(jù)(此方法不單獨(dú)解釋嘲驾,請(qǐng)查看示例代碼淌哟,有詳細(xì)注釋說明),才能判斷現(xiàn)在的size有沒有達(dá)到閥值threshhold辽故,如果沒有要清除的數(shù)據(jù)徒仓,存儲(chǔ)元素個(gè)數(shù)仍然 大于 閾值 則調(diào)用rehash方法進(jìn)行擴(kuò)容(此方法不單獨(dú)解釋,請(qǐng)查看示例代碼誊垢,有詳細(xì)注釋說明)掉弛。
示例代碼:
/**
* 設(shè)置對(duì)應(yīng)ThreadLocal的值
*
* @param key 當(dāng)前thread local 對(duì)象
* @param value 要設(shè)置的值
*/
private void set(ThreadLocal<?> key, Object value) {
// 我們不會(huì)像get()方法那樣使用快速設(shè)置的方式,
// 因?yàn)橥ǔ:苌偈褂胹et()方法去創(chuàng)建新的實(shí)體
// 相對(duì)于替換一個(gè)已經(jīng)存在的實(shí)體, 在這種情況下,
// 快速設(shè)置方案會(huì)經(jīng)常失敗喂走。
// 獲取對(duì)應(yīng)的底層哈希表 table
Entry[] tab = table;
// 獲取哈希表長(zhǎng)度
int len = tab.length;
// 計(jì)算對(duì)應(yīng)threalocal的存儲(chǔ)位置
int i = key.threadLocalHashCode & (len-1);
// 循環(huán)遍歷table對(duì)應(yīng)該位置的實(shí)體殃饿,查找對(duì)應(yīng)的threadLocal
for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {
// 獲取當(dāng)前位置的ThreadLocal
ThreadLocal<?> k = e.get();
// 如果key threadLocal一致,則證明找到對(duì)應(yīng)的threadLocal
if (k == key) {
// 賦予新值
e.value = value;
// 結(jié)束
return;
}
// 如果當(dāng)前位置的key threadLocal為null
if (k == null) {
// 替換該位置key == null 的實(shí)體為當(dāng)前要設(shè)置的實(shí)體
replaceStaleEntry(key, value, i);
// 結(jié)束
return;
}
}
// 當(dāng)前位置的k 芋肠!= key && k != null
// 創(chuàng)建新的實(shí)體乎芳,并存放至當(dāng)前位置i
tab[i] = new Entry(key, value);
// 實(shí)際存儲(chǔ)鍵值對(duì)元素個(gè)數(shù) + 1
int sz = ++size;
// 由于弱引用帶來了這個(gè)問題,所以先要清除無用數(shù)據(jù),才能判斷現(xiàn)在的size有沒有達(dá)到閥值threshhold
// 如果沒有要清除的數(shù)據(jù)奈惑,存儲(chǔ)元素個(gè)數(shù)仍然 大于 閾值 則擴(kuò)容
if (!cleanSomeSlots(i, sz) && sz >= threshold)
// 擴(kuò)容
rehash();
}
/**
* 當(dāng)執(zhí)行set操作時(shí)谬晕,獲取對(duì)應(yīng)的key threadLocal,并替換過期的實(shí)體
* 將這個(gè)value值存儲(chǔ)在對(duì)應(yīng)key threadLocal的實(shí)體中携取,無論是否已經(jīng)存在體
* 對(duì)應(yīng)的key threadLocal
*
* 有一個(gè)副作用, 此方法會(huì)刪除該位置下和該位置nextIndex對(duì)應(yīng)的所有過期的實(shí)體
*
* @param key 當(dāng)前thread local 對(duì)象
* @param value 當(dāng)前thread local 對(duì)象對(duì)應(yīng)存儲(chǔ)的值
* @param staleSlot 第一次找到此過期的實(shí)體對(duì)應(yīng)的位置索引index
* .
*/
private void replaceStaleEntry(ThreadLocal<?> key, Object value,
int staleSlot) {
// 獲取對(duì)應(yīng)的底層哈希表 table
Entry[] tab = table;
// 獲取哈希表長(zhǎng)度
int len = tab.length;
Entry e;
// 往前找攒钳,找到table中第一個(gè)過期的實(shí)體的下標(biāo)
// 清理整個(gè)table是為了避免因?yàn)槔厥諑淼倪B續(xù)增長(zhǎng)哈希的危險(xiǎn)
// 也就是說,哈希表沒有清理干凈雷滋,當(dāng)GC到來的時(shí)候不撑,后果很嚴(yán)重
// 記錄要清除的位置的起始首位置
int slotToExpunge = staleSlot;
// 從該位置開始,往前遍歷查找第一個(gè)過期的實(shí)體的下標(biāo)
for (int i = prevIndex(staleSlot, len);
(e = tab[i]) != null;
i = prevIndex(i, len))
if (e.get() == null)
slotToExpunge = i;
// 找到key一致的ThreadLocal或找到一個(gè)key為 null的
for (int i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
// 如果我們找到了key晤斩,那么我們就需要把它跟新的過期數(shù)據(jù)交換來保持哈希表的順序
// 那么剩下的過期Entry呢焕檬,就可以交給expungeStaleEntry方法來擦除掉
// 將新設(shè)置的實(shí)體放置在此過期的實(shí)體的位置上
if (k == key) {
// 替換,將要設(shè)置的值放在此過期的實(shí)體中
e.value = value;
tab[i] = tab[staleSlot];
tab[staleSlot] = e;
// 如果存在澳泵,則開始清除之前過期的實(shí)體
if (slotToExpunge == staleSlot)
slotToExpunge = i;
// 在這里開始清除過期數(shù)據(jù)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}
// / 如果我們沒有在往后查找中找沒有找到過期的實(shí)體实愚,
// 那么slotToExpunge就是第一個(gè)過期Entry的下標(biāo)了
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}
// 最后key仍沒有找到,則將要設(shè)置的新實(shí)體放置
// 在原過期的實(shí)體對(duì)應(yīng)的位置上兔辅。
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);
// 如果該位置對(duì)應(yīng)的其他關(guān)聯(lián)位置存在過期實(shí)體腊敲,則清除
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
/**
* 啟發(fā)式的掃描查找一些過期的實(shí)體并清除,
* 此方法會(huì)再添加新實(shí)體的時(shí)候被調(diào)用,
* 或者過期的元素被清除時(shí)也會(huì)被調(diào)用.
* 如果實(shí)在沒有過期數(shù)據(jù)维苔,那么這個(gè)算法的時(shí)間復(fù)雜度就是O(log n)
* 如果有過期數(shù)據(jù)碰辅,那么這個(gè)算法的時(shí)間復(fù)雜度就是O(n)
*
* @param i 一個(gè)確定不是過期的實(shí)體的位置,從這個(gè)位置i開始掃描
*
* @param n 掃描控制: 有{@code log2(n)} 單元會(huì)被掃描,
* 除非找到了過期的實(shí)體, 在這種情況下
* 有{@code log2(table.length)-1} 的格外單元會(huì)被掃描.
* 當(dāng)調(diào)用插入時(shí), 這個(gè)參數(shù)的值是存儲(chǔ)實(shí)體的個(gè)數(shù)介时,
* 但如果調(diào)用 replaceStaleEntry方法, 這個(gè)值是哈希表table的長(zhǎng)度
* (注意: 所有的這些都可能或多或少的影響n的權(quán)重
* 但是這個(gè)版本簡(jiǎn)單没宾,快速,而且似乎執(zhí)行效率還可以)
*
* @return true 返回true沸柔,如果有任何過期的實(shí)體被刪除循衰。
*/
private boolean cleanSomeSlots(int i, int n) {
boolean removed = false;
Entry[] tab = table;
int len = tab.length;
do {
i = nextIndex(i, len);
Entry e = tab[i];
if (e != null && e.get() == null) {
n = len;
removed = true;
i = expungeStaleEntry(i);
}
} while ( (n >>>= 1) != 0);
return removed;
}
/**
* 哈希表擴(kuò)容方法
* 首先掃描整個(gè)哈希表table,刪除過期的實(shí)體
* 縮小哈希表table大小 或 擴(kuò)大哈希表table大小褐澎,擴(kuò)大的容量是加倍.
*/
private void rehash() {
// 刪除所有過期的實(shí)體
expungeStaleEntries();
// 使用較低的閾值threshold加倍以避免滯后
// 存儲(chǔ)實(shí)體個(gè)數(shù) 大于等于 閾值的3/4則擴(kuò)容
if (size >= threshold - threshold / 4)
resize();
}
/**
* 擴(kuò)容方法会钝,以2倍的大小進(jìn)行擴(kuò)容
* 擴(kuò)容的思想跟HashMap很相似,都是把容量擴(kuò)大兩倍
* 不同之處還是因?yàn)閃eakReference帶來的
*/
private void resize() {
// 記錄舊的哈希表
Entry[] oldTab = table;
// 記錄舊的哈希表長(zhǎng)度
int oldLen = oldTab.length;
// 新的哈希表長(zhǎng)度為舊的哈希表長(zhǎng)度的2倍
int newLen = oldLen * 2;
// 創(chuàng)建新的哈希表
Entry[] newTab = new Entry[newLen];
int count = 0;
// 逐一遍歷舊的哈希表table的每個(gè)實(shí)體乱凿,重新分配至新的哈希表中
for (int j = 0; j < oldLen; ++j) {
// 獲取對(duì)應(yīng)位置的實(shí)體
Entry e = oldTab[j];
// 如果實(shí)體不會(huì)null
if (e != null) {
// 獲取實(shí)體對(duì)應(yīng)的ThreadLocal
ThreadLocal<?> k = e.get();
// 如果該ThreadLocal 為 null
if (k == null) {
// 則對(duì)應(yīng)的值也要清除
// 就算是擴(kuò)容顽素,也不能忘了為擦除過期數(shù)據(jù)做準(zhǔn)備
e.value = null; // Help the GC
} else {
// 如果不是過期實(shí)體咽弦,則根據(jù)新的長(zhǎng)度重新計(jì)算存儲(chǔ)位置
int h = k.threadLocalHashCode & (newLen - 1);
// 將該實(shí)體存儲(chǔ)在對(duì)應(yīng)ThreadLocal的最后一個(gè)位置
while (newTab[h] != null)
h = nextIndex(h, newLen);
newTab[h] = e;
count++;
}
}
}
// 重新分配位置完畢徒蟆,則重新計(jì)算閾值Threshold
setThreshold(newLen);
// 記錄實(shí)際存儲(chǔ)元素個(gè)數(shù)
size = count;
// 將新的哈希表賦值至底層table
table = newTab;
}
ThreadLocal的remove()操作實(shí)際是調(diào)用ThreadLocalMap的remove(ThreadLocal<?> key)方法,該方法進(jìn)行了如下操作:
- 1.獲取對(duì)應(yīng)的底層哈希表 table型型,計(jì)算對(duì)應(yīng)threalocal的存儲(chǔ)位置段审。
- 2.循環(huán)遍歷table對(duì)應(yīng)該位置的實(shí)體,查找對(duì)應(yīng)的threadLocal闹蒜。
- 3 .獲取當(dāng)前位置的threadLocal寺枉,如果key threadLocal一致抑淫,則證明找到對(duì)應(yīng)的threadLocal,執(zhí)行刪除操作姥闪,刪除此位置的實(shí)體始苇,結(jié)束。
示例代碼:
/**
* 移除對(duì)應(yīng)ThreadLocal的實(shí)體
*/
private void remove(ThreadLocal<?> key) {
// 獲取對(duì)應(yīng)的底層哈希表 table
Entry[] tab = table;
// 獲取哈希表長(zhǎng)度
int len = tab.length;
// 計(jì)算對(duì)應(yīng)threalocal的存儲(chǔ)位置
int i = key.threadLocalHashCode & (len-1);
// 循環(huán)遍歷table對(duì)應(yīng)該位置的實(shí)體筐喳,查找對(duì)應(yīng)的threadLocal
for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {
// 如果key threadLocal一致催式,則證明找到對(duì)應(yīng)的threadLocal
if (e.get() == key) {
// 執(zhí)行清除操作
e.clear();
// 清除此位置的實(shí)體
expungeStaleEntry(i);
// 結(jié)束
return;
}
}
}
5.ThreadLocalMap中的存儲(chǔ)實(shí)體Entry使用ThreadLocal作為key,但這個(gè)Entry是繼承弱引用WeakReference的避归,為什么要這樣設(shè)計(jì)荣月,使用了弱引用WeakReference會(huì)造成內(nèi)存泄露問題嗎?
首先梳毙,回答這個(gè)問題之前哺窄,我需要解釋一下什么是強(qiáng)引用,什么是弱引用账锹。
我們?cè)谡G闆r下萌业,普遍使用的是強(qiáng)引用:
A a = new A();
B b = new B();
當(dāng) a = null;b = null;時(shí),一段時(shí)間后奸柬,JAVA垃圾回收機(jī)制GC會(huì)將 a 和 b 對(duì)應(yīng)所分配的內(nèi)存空間給回收咽白。
但考慮這樣一種情況:
C c = new C(b);
b = null;
當(dāng) b 被設(shè)置成null時(shí),那么是否意味這一段時(shí)間后GC工作可以回收 b 所分配的內(nèi)存空間呢鸟缕?答案是否定的晶框,因?yàn)榧词?b 被設(shè)置成null,但 c 仍然持有對(duì) b 的引用懂从,而且還是強(qiáng)引用授段,所以GC不會(huì)回收 b 原先所分配的空間,既不能回收番甩,又不能使用侵贵,這就造成了 內(nèi)存泄露。
那么如何處理呢缘薛?
可以通過c = null;窍育,也可以使用弱引用WeakReference w = new WeakReference(b);。因?yàn)槭褂昧巳跻肳eakReference宴胧,GC是可以回收 b 原先所分配的空間的漱抓。
詳解請(qǐng)見:ThreadLocal 內(nèi)存泄露的實(shí)例分析
6.ThreadLocal和synchronized的區(qū)別?
ThreadLocal和synchronized關(guān)鍵字都用于處理多線程并發(fā)訪問變量的問題,只是二者處理問題的角度和思路不同恕齐。
- ThreadLocal是一個(gè)Java類,通過對(duì)當(dāng)前線程中的局部變量的操作來解決不同線程的變量訪問的沖突問題乞娄。所以,ThreadLocal提供了線程安全的共享對(duì)象機(jī)制,每個(gè)線程都擁有其副本仪或。
- Java中的synchronized是一個(gè)保留字确镊,它依靠JVM的鎖機(jī)制來實(shí)現(xiàn)臨界區(qū)的函數(shù)或者變量的訪問中的原子性。在同步機(jī)制中范删,通過對(duì)象的鎖機(jī)制保證同一時(shí)間只有一個(gè)線程訪問變量蕾域。此時(shí),被用作“鎖機(jī)制”的變量時(shí)多個(gè)線程共享的到旦。
- 同步機(jī)制(synchronized關(guān)鍵字)采用了以“時(shí)間換空間”的方式束铭,提供一份變量,讓不同的線程排隊(duì)訪問厢绝。而ThreadLocal采用了“以空間換時(shí)間”的方式契沫,為每一個(gè)線程都提供一份變量的副本,從而實(shí)現(xiàn)同時(shí)訪問而互不影響昔汉。
7.ThreadLocal在現(xiàn)時(shí)有什么應(yīng)用場(chǎng)景懈万?
總的來說ThreadLocal主要是解決2種類型的問題:
- 解決并發(fā)問題:使用ThreadLocal代替synchronized來保證線程安全。同步機(jī)制采用了“以時(shí)間換空間”的方式靶病,而ThreadLocal采用了“以空間換時(shí)間”的方式会通。前者僅提供一份變量,讓不同的線程排隊(duì)訪問娄周,而后者為每一個(gè)線程都提供了一份變量涕侈,因此可以同時(shí)訪問而互不影響。
- 解決數(shù)據(jù)存儲(chǔ)問題:ThreadLocal為變量在每個(gè)線程中都創(chuàng)建了一個(gè)副本煤辨,所以每個(gè)線程可以訪問自己內(nèi)部的副本變量裳涛,不同線程之間不會(huì)互相干擾。如一個(gè)Parameter對(duì)象的數(shù)據(jù)需要在多個(gè)模塊中使用众辨,如果采用參數(shù)傳遞的方式端三,顯然會(huì)增加模塊之間的耦合性。此時(shí)我們可以使用ThreadLocal解決鹃彻。
應(yīng)用場(chǎng)景:
- Spring使用ThreadLocal解決線程安全問題
我們知道在一般情況下郊闯,只有無狀態(tài)的Bean才可以在多線程環(huán)境下共享,在Spring中蛛株,絕大部分Bean都可以聲明為singleton作用域团赁。就是因?yàn)镾pring對(duì)一些Bean(如RequestContextHolder、TransactionSynchronizationManager谨履、LocaleContextHolder等)中非線程安全狀態(tài)采用ThreadLocal進(jìn)行處理欢摄,讓它們也成為線程安全的狀態(tài),因?yàn)橛袪顟B(tài)的Bean就可以在多線程中共享了屉符。- 一般的Web應(yīng)用劃分為展現(xiàn)層剧浸、服務(wù)層和持久層三個(gè)層次锹引,在不同的層中編寫對(duì)應(yīng)的邏輯矗钟,下層通過接口向上層開放功能調(diào)用唆香。在一般情況下,從接收請(qǐng)求到返回響應(yīng)所經(jīng)過的所有程序調(diào)用都同屬于一個(gè)線程ThreadLocal是解決線程安全問題一個(gè)很好的思路吨艇,它通過為每個(gè)線程提供一個(gè)獨(dú)立的變量副本解決了變量并發(fā)訪問的沖突問題躬它。在很多情況下,ThreadLocal比直接使用synchronized同步機(jī)制解決線程安全問題更簡(jiǎn)單东涡,更方便冯吓,且結(jié)果程序擁有更高的并發(fā)性
public abstract class RequestContextHolder {
····
private static final boolean jsfPresent =
ClassUtils.isPresent("javax.faces.context.FacesContext", RequestContextHolder.class.getClassLoader());
private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
new NamedThreadLocal<RequestAttributes>("Request attributes");
private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
new NamedInheritableThreadLocal<RequestAttributes>("Request context");
·····
}
總結(jié)
- ThreadLocal提供線程內(nèi)部的局部變量,在本線程內(nèi)隨時(shí)隨地可取疮跑,隔離其他線程组贺。
- ThreadLocal的設(shè)計(jì)是:每個(gè)Thread維護(hù)一個(gè)ThreadLocalMap哈希表,這個(gè)哈希表的key是ThreadLocal實(shí)例本身祖娘,value才是真正要存儲(chǔ)的值Object失尖。
- 對(duì)ThreadLocal的常用操作實(shí)際是對(duì)線程Thread中的ThreadLocalMap進(jìn)行操作。
- ThreadLocalMap的底層實(shí)現(xiàn)是一個(gè)定制的自定義HashMap哈希表渐苏,ThreadLocalMap的閾值threshold = 底層哈希表table的長(zhǎng)度 len * 2 / 3掀潮,當(dāng)實(shí)際存儲(chǔ)元素個(gè)數(shù)size 大于或等于 閾值threshold的 3/4 時(shí)size >= threshold*3/4,則對(duì)底層哈希表數(shù)組table進(jìn)行擴(kuò)容操作琼富。
- ThreadLocalMap中的哈希表Entry[] table存儲(chǔ)的核心元素是Entry仪吧,存儲(chǔ)的key是ThreadLocal實(shí)例對(duì)象,value是ThreadLocal 對(duì)應(yīng)儲(chǔ)存的值value鞠眉。需要注意的是薯鼠,此Entry繼承了弱引用 WeakReference,所以在使用ThreadLocalMap時(shí)械蹋,發(fā)現(xiàn)key == null人断,則意味著此key ThreadLocal不在被引用,需要將其從ThreadLocalMap哈希表中移除朝蜘。
- ThreadLocalMap使用ThreadLocal的弱引用作為key恶迈,如果一個(gè)ThreadLocal沒有外部強(qiáng)引用來引用它,那么系統(tǒng) GC 的時(shí)候谱醇,這個(gè)ThreadLocal勢(shì)必會(huì)被回收暇仲。所以,在ThreadLocal的get(),set(),remove()的時(shí)候都會(huì)清除線程ThreadLocalMap里所有key為null的value副渴。如果我們不主動(dòng)調(diào)用上述操作奈附,則會(huì)導(dǎo)致內(nèi)存泄露。
- 為了安全地使用ThreadLocal煮剧,必須要像每次使用完鎖就解鎖一樣斥滤,在每次使用完ThreadLocal后都要調(diào)用remove()來清理無用的Entry将鸵。這在操作在使用線程池時(shí)尤為重要。
- ThreadLocal和synchronized的區(qū)別:同步機(jī)制(synchronized關(guān)鍵字)采用了以“時(shí)間換空間”的方式佑颇,提供一份變量顶掉,讓不同的線程排隊(duì)訪問。而ThreadLocal采用了“以空間換時(shí)間”的方式挑胸,為每一個(gè)線程都提供一份變量的副本痒筒,從而實(shí)現(xiàn)同時(shí)訪問而互不影響。
- ThreadLocal主要是解決2種類型的問題:A. 解決并發(fā)問題:使用ThreadLocal代替同步機(jī)制解決并發(fā)問題茬贵。B. 解決數(shù)據(jù)存儲(chǔ)問題:如一個(gè)Parameter對(duì)象的數(shù)據(jù)需要在多個(gè)模塊中使用簿透,如果采用參數(shù)傳遞的方式,顯然會(huì)增加模塊之間的耦合性解藻。此時(shí)我們可以使用ThreadLocal解決老充。