前言
Github:https://github.com/yihonglei/thinking-in-concurrent
一 ThreadLocal原理
ThreadLocal的作用就是讓每個線程綁定自己的局部變量,用於存儲每個線程的私有數據,
在線程的生命週期內起作用,減少同一個線程內多個函數或者組件之間一些公共變量傳遞的複雜性。
1、創建一個ThreadLocal對象
private ThreadLocal myThreadLocal = new ThreadLocal();
實例化了一個ThreadLocal對象。每個線程僅需要實例化一次即可。
雖然不同的線程執行同一段代碼時,訪問同一個ThreadLocal變量,但是每個線程只能看到私有的ThreadLocal實例。
所以不同的線程在給ThreadLocal對象設置不同的值時,他們也不能看到彼此的修改。
2、訪問ThreadLocal對象
一旦創建了一個ThreadLocal對象,你就可以通過以下方式來存儲此對象的值:
myThreadLocal.set("A thread local value");
也可以直接讀取一個ThreadLocal對象的值:
String threadLocalValue = (String) myThreadLocal.get();
get()方法會返回一個Object對象,而set()方法則依賴一個Object對象參數。
3、ThreadLocal泛型
爲了使get()方法返回值不用做強制類型轉換,通常可以創建一個泛型化的ThreadLocal對象。
private ThreadLocal<String> myThreadLocal1 = new ThreadLocal();
現在你可以存儲一個字符串到ThreadLocal實例裏,此外,當你從此ThreadLocal實例中獲取值的時候,
就不必要做強制類型轉換。
myThreadLocal1.set("Hello ThreadLocal");
String threadLocalValues = myThreadLocal.get();
4、初始化ThreadLocal
由於ThreadLocal對象的set()方法設置的值只對當前線程可見,那有什麼方法可以爲ThreadLocal對象設置的值對
所有線程都可見。爲此,我們可以通過ThreadLocal子類的實現,並覆寫initialValue()方法,就可以爲ThreadLocal對象
指定一個初始化值。如下所示:
private ThreadLocal myThreadLocal = new ThreadLocal<String>() {
@Override protected String initialValue() {
return "This is the initial value";
}
};
此時,在set()方法調用前,當調用get()方法的時候,所有線程都可以看到同一個初始化值。
二 ThreadLocal實戰
1、ThreadLocal類的get方法和null值
package com.jpeony.concurrent.threadlocal;
/**
* ThreadLocal類的get方法和null值
*
* @author yihonglei
*/
public class RunSimpleTest {
public static ThreadLocal tl = new ThreadLocal();
public static void main(String[] args) {
if (tl.get() == null) {
System.out.println("未放入值");
tl.set("放入值");
}
// 獲取值
System.out.println(tl.get());
}
}
當第一次調用get方法時,由於沒有放入過值,返回null,通過set方法放入值,之後通過get方法可以獲取當前線程的私有數據。
2、ThreadLocal隔離線實驗
創建一個數據工具類:
package com.jpeony.concurrent.threadlocal;
/**
* @author yihonglei
*/
public class DataTools {
public static ThreadLocal<String> tl = new ThreadLocal<>();
}
創建線程A:
package com.jpeony.concurrent.threadlocal;
/**
* @author yihonglei
*/
public class ThreadA extends Thread {
@Override
public void run() {
try {
for (int i = 0; i < 100; i++) {
DataTools.tl.set("A_Thread_" + System.currentTimeMillis());
// 獲取當前線程私有數據
System.out.println("線程A獲取的值:" + DataTools.tl.get());
Thread.sleep(200);
}
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
創建線程B:
package com.jpeony.concurrent.threadlocal;
/**
* @author yihonglei
*/
public class ThreadB extends Thread {
@Override
public void run() {
try {
for (int i = 0; i < 100; i++) {
DataTools.tl.set("B_Thread_" + System.currentTimeMillis());
// 獲取當前線程私有數據
System.out.println("線程B獲取的值:" + DataTools.tl.get());
Thread.sleep(200);
}
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
測試類:
package com.jpeony.concurrent.threadlocal;
/**
* @author yihonglei
*/
public class RunTest {
public static void main(String[] args) {
try {
ThreadA threadA = new ThreadA();
threadA.start();
Thread.sleep(3000);
ThreadB threadB = new ThreadB();
threadB.start();
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
運行結果:
從程序運行結果,線程獲取屬於自己的私有數據,證明了ThreadLocal實現線程私有數據保證數據的隔離性。
3、如何讓get方法第一次獲取的值不爲null
寫一個類繼承與ThreadLocal,重寫initialValue()方法,設置get的默認值。
package com.jpeony.concurrent.threadlocal;
/**
* 重寫ThreadLocal初始化方法,自定義初始值
*
* @author yihonglei
*/
public class ThreadLocalExt extends ThreadLocal {
@Override
protected Object initialValue() {
//return super.initialValue();
return "設置默認值,第一次獲取不在爲null";
}
}
三 ThreadLocal源碼分析
1、ThreadLocal源碼結構
1.1 主要結構說明
SuppliedThreadLocal:jdk8的增強,支持lambda表達式;
ThreadLocalMap:ThreadLocal內部數據結構,類似Map,但是key是對應ThreadLocal對象的弱引用;
initilaValue():ThreadLocal初始值;
set(T):往ThreadLocalMap放值;
get():從ThreadLocalMap取值;
1.2 ThreadLocal如何實現線程私有的?
在Thread裏面,有一個局部變量ThreadLocal.ThreadLocalMap threadLocals = null;
通過這個局部變量維護線程自己的一份數據,這個局部變量值持續到線程結束。
至於這個ThreadLocalMap是個什麼,下面分析它在ThreadLocal中的地位和數據結構。
Thread的局部變量threadLocals是私有的,數據結構是ThreadLocal的ThreadLocalMap,如果一個線程想擁有
多個ThreadLocal,則new多個ThreadLocal對象,根據對象計算key,都存在ThreadLoca.ThreadLocalMap數據結構裏面。
2、set過程源碼分析
2.1 set(T value)
set值
public void set(T value) {
// 獲取當前線程
Thread t = Thread.currentThread();
// 獲取ThreadLocalMap
ThreadLocalMap map = getMap(t);
// 判斷是否已經存在ThreadLocalMap
if (map != null)
// 添加值到ThreadLocalMap
map.set(this, value);
else
// 創建ThreadLocalMap並添加值
createMap(t, value);
}
2.2 getMap(Thread t)
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
threadLocals爲ThreadLocal的內部類成員變量ThreadLocalMap,第一次添加值獲取的是null,這個時候還沒有初始化。
ThreadLocal.ThreadLocalMap threadLocals = null;
2.3 createMap(Thread t, T firstValue):
void createMap(Thread t, T firstValue) {
// 初始化並set值
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
我們先看下ThreadLocalMap是啥?
2.4 ThreadLocalMap是啥?
ThreadLocalMap是ThreadLocal靜態內部類,專爲ThreadLocal定製的高效實現,基於弱引用的垃圾清理機制。
ThreadLocalMap源碼結構:
ThreadLocalMap存儲結構:
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*
* key是對ThreadLocal的弱引用,value是一個Object,存儲要放入ThreadLocal的值。
* 當沒有引用的時候,Entry自動從table中剔除。
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
// 存儲要放入ThreadLocal的值
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
結構類似Map,Entry繼承WeakReference(弱引用),key跟普通Map的key有區別,這個key存放的是對ThreadLocal的弱引用,
當對key的引用不存在的時候,自動從Entry數組移出,被GC自動回收,弱引用的回收,GC是不判斷內存是否還夠的,
Object對象存儲要放入ThreadLocal的值。debug可以看下這個key長的樣子。
ThreadLocalMap成員變量:
/**
* The initial capacity -- MUST be a power of two.
* 初始化容量大小,必須是2的冪次方
*/
private static final int INITIAL_CAPACITY = 16;
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
* table是一個Entry數組
*/
private Entry[] table;
/**
* The number of entries in the table.
* table的元素個數(Entry數組的元素個數)
*/
private int size = 0;
/**
* The next size value at which to resize.
* 重新分配表大小的閥值,默認值是0
*/
private int threshold; // Default to 0
ThreadLocalMap主要方法:
/**
* Set the resize threshold to maintain at worst a 2/3 load factor.
* 設置閥值上限,不能超過最壞2/3負載因子,因爲超過之後性能極速下降
*/
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
/**
* Increment i modulo len.
* 環形意義上的下一個索引
*/
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
/**
* Decrement i modulo len.
* 環形意義上的上一個索引
*/
private static int prevIndex(int i, int len) {
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
ThreadLocalMap使用線性探測法來解決散列衝突,所以實際上Entry[]數組在程序邏輯上是作爲一個環形存在的。
圖片源於網絡。
ThreadLocalMap維護了Entry環形數組,數組中元素Entry的邏輯上的key爲某個ThreadLocal對象(實際上是指向該ThreadLocal對
象的弱引用),value爲代碼中該線程往該ThreadLoacl變量實際塞入的值。
ThreadLocalMap構造函數:
/**
* Construct a new map initially containing (firstKey, firstValue).
* ThreadLocalMaps are constructed lazily, so we only create
* one when we have at least one entry to put in it.
* 構造一個包含firstKey和firstValue的map。
* ThreadLocalMap是惰性構造的,所以只有當至少要往裏面放一個元素的時候纔會構建它。
*/
hreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
// 初始化table,設置Entry數組初始容量爲16
table = new Entry[INITIAL_CAPACITY];
// firstKey的threadLocalHashCode與初始大小(16-1)取模算entry的下標值
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
// 初始化節點
table[i] = new Entry(firstKey, firstValue);
size = 1;
// 設置擴容的閥值,當達到這個值的時候,Entry數據要進行擴容處理
setThreshold(INITIAL_CAPACITY);
}
ThreadLocalMap的set方法:
/**
* Set the value associated with key.
*
* @param key the thread local object
* @param value the value to be set
*/
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
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)]) {
// 找到對應的entry
ThreadLocal<?> k = e.get();
// 獲取對應的value
if (k == key) {
e.value = value;
return;
}
// 替換失效的entry
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
// 在table中沒有找到對應的key,會基於k-v構建節點,並賦值到Entry對應的位置
tab[i] = new Entry(key, value);
int sz = ++size;
// 從數組中移出空閒的entry
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
/**
* Replace a stale entry encountered during a set operation
* with an entry for the specified key. The value passed in
* the value parameter is stored in the entry, whether or not
* an entry already exists for the specified key.
*
* As a side effect, this method expunges all stale entries in the
* "run" containing the stale entry. (A run is a sequence of entries
* between two null slots.)
*
* @param key the key
* @param value the value to be associated with key
* @param staleSlot index of the first stale entry encountered while
* searching for key.
*/
rivate void replaceStaleEntry(ThreadLocal<?> key, Object value,
int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e;
// Back up to check for prior stale entry in current run.
// We clean out whole runs at a time to avoid continual
// incremental rehashing due to garbage collector freeing
// up refs in bunches (i.e., whenever the collector runs).
// 向前遍歷Entry數組,查找最近一個無效的slot
int slotToExpunge = staleSlot;
for (int i = prevIndex(staleSlot, len);
(e = tab[i]) != null;
i = prevIndex(i, len))
if (e.get() == null)
slotToExpunge = i;
// Find either the key or trailing null slot of run, whichever
// occurs first
// 向後遍歷
for (int i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
// 找到key
ThreadLocal<?> k = e.get();
// If we find key, then we need to swap it
// with the stale entry to maintain hash table order.
// The newly stale slot, or any other stale slot
// encountered above it, can then be sent to expungeStaleEntry
// to remove or rehash all of the other entries in run.
// 找到了key,把新值替換舊值
if (k == key) {
e.value = value;
tab[i] = tab[staleSlot];
tab[staleSlot] = e;
// Start expunge at preceding stale entry if it exists
// 如果找到了無效的slot,則這個作爲清理的起點,否則,當前的值作爲清理的起點
if (slotToExpunge == staleSlot)
slotToExpunge = i;
// 從slotToExpunge開始做一次連續段的清理,再做一次啓發式清理
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}
// If we didn't find stale entry on backward scan, the
// first stale entry seen while scanning for key is the
// first still present in the run.
// 如果當前的slot已經無效,並且向前掃描過程中沒有無效slot,則更新slotToExpunge爲當前位置
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}
// If key not found, put new entry in stale slot
// 如果key在table中不存在,則在原地放一個即可
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);
// If there are any other stale entries in run, expunge them
// 在探測過程中如果發現任何無效slot,則做一次清理(連續段清理+啓發式清理)
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
/**
* Heuristically scan some cells looking for stale entries.
* This is invoked when either a new element is added, or
* another stale one has been expunged. It performs a
* logarithmic number of scans, as a balance between no
* scanning (fast but retains garbage) and a number of scans
* proportional to number of elements, that would find all
* garbage but would cause some insertions to take O(n) time.
*
* @param i a position known NOT to hold a stale entry. The
* scan starts at the element after i.
*
* @param n scan control: {@code log2(n)} cells are scanned,
* unless a stale entry is found, in which case
* {@code log2(table.length)-1} additional cells are scanned.
* When called from insertions, this parameter is the number
* of elements, but when from replaceStaleEntry, it is the
* table length. (Note: all this could be changed to be either
* more or less aggressive by weighting n instead of just
* using straight log n. But this version is simple, fast, and
* seems to work well.)
*
* @return true if any stale entries have been removed.
*/
private boolean cleanSomeSlots(int i, int n) {
boolean removed = false;
Entry[] tab = table;
int len = tab.length;
do {
// i是剛構建的元素下標,所以不會是一個無效slot,所以從下一個開始判斷
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;
}
/**
* Expunge a stale entry by rehashing any possibly colliding entries
* lying between staleSlot and the next null slot. This also expunges
* any other stale entries encountered before the trailing null. See
* Knuth, Section 6.4
*
* @param staleSlot index of slot known to have null key
* @return the index of the next null slot after staleSlot
* (all between staleSlot and this slot will have been checked
* for expunging).
*/
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// expunge entry at staleSlot
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// Rehash until we encounter null
Entry e;
int i;
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;
// Unlike Knuth 6.4 Algorithm R, we must scan until
// null because multiple entries could have been stale.
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
/**
* Re-pack and/or re-size the table. First scan the entire
* table removing stale entries. If this doesn't sufficiently
* shrink the size of the table, double the table size.
*/
private void rehash() {
expungeStaleEntries();
// Use lower threshold for doubling to avoid hysteresis
/*
* 因爲做了一次清理,所以size很可能會變小。
* ThreadLocalMap這裏的實現是調低閾值來判斷是否需要擴容,
* threshold默認爲len*2/3,所以這裏的threshold - threshold / 4相當於len/2
*/
if (size >= threshold - threshold / 4)
resize();
}
/**
* Expunge all stale entries in the table.
*/
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);
}
}
/**
* Double the capacity of the table.
* 擴容,因爲需要保證table的容量len爲2的冪,所以擴容即擴大2倍
*/
private void resize() {
Entry[] oldTab = table;
int oldLen = oldTab.length;
int newLen = oldLen * 2;
Entry[] newTab = new Entry[newLen];
int count = 0;
for (int j = 0; j < oldLen; ++j) {
Entry e = oldTab[j];
if (e != null) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null; // Help the GC
} else {
// 線性探測來存放Entry
int h = k.threadLocalHashCode & (newLen - 1);
while (newTab[h] != null)
h = nextIndex(h, newLen);
newTab[h] = e;
count++;
}
}
}
setThreshold(newLen);
size = count;
table = newTab;
}
ThreadLocalMap的getEntry方法:
/**
* Get the entry associated with key. This method
* itself handles only the fast path: a direct hit of existing
* key. It otherwise relays to getEntryAfterMiss. This is
* designed to maximize performance for direct hits, in part
* by making this method readily inlinable.
*
* @param key the thread local object
* @return the entry associated with key, or null if no such
*/
private Entry getEntry(ThreadLocal<?> key) {
// 根據key這個ThreadLocal的ID來獲取索引,也即哈希值
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
// 對應的entry存在且未失效且弱引用指向的ThreadLocal就是key,則命中返回
if (e != null && e.get() == key)
return e;
else
// 因爲用的是線性探測,所以往後找還是有可能能夠找到目標Entry的。
return getEntryAfterMiss(key, i, e);
}
/**
* Version of getEntry method for use when key is not found in
* its direct hash slot.
*
* @param key the thread local object
* @param i the table index for key's hash code
* @param e the entry at table[i]
* @return the entry associated with key, or null if no such
*/
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
// 基於線性探測法不斷向後探測直到遇到空entry
while (e != null) {
ThreadLocal<?> k = e.get();
// 找到目標
if (k == key)
return e;
if (k == null)
// 該entry對應的ThreadLocal已經被回收,調用expungeStaleEntry來清理無效的entry
expungeStaleEntry(i);
else
// 環形意義下往後面走
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
/**
* Expunge a stale entry by rehashing any possibly colliding entries
* lying between staleSlot and the next null slot. This also expunges
* any other stale entries encountered before the trailing null. See
* Knuth, Section 6.4
*
* @param staleSlot index of slot known to have null key
* @return the index of the next null slot after staleSlot
* (all between staleSlot and this slot will have been checked
* for expunging).
*/
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// expunge entry at staleSlot
// 因爲entry對應的ThreadLocal已經被回收,value設爲null,顯式斷開強引用
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// Rehash until we encounter null
Entry e;
int i;
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;
// Unlike Knuth 6.4 Algorithm R, we must scan until
// null because multiple entries could have been stale.
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
3、get過程源碼分析
/**
* Get the entry associated with key. This method
* itself handles only the fast path: a direct hit of existing
* key. It otherwise relays to getEntryAfterMiss. This is
* designed to maximize performance for direct hits, in part
* by making this method readily inlinable.
*
* @param key the thread local object
* @return the entry associated with key, or null if no such
*/
private Entry getEntry(ThreadLocal<?> key) {
// 根據key,即ThreadLocal的哈希值計算索引
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
// 如果對應的Entry存在,並且ThreadLocal的弱引用指向key,則key命中返回
if (e != null && e.get() == key)
return e;
else
// 如果直接查找不到,根據當前位置往後繼續找
return getEntryAfterMiss(key, i, e);
}
/**
* Version of getEntry method for use when key is not found in
* its direct hash slot.
*
* @param key the thread local object
* @param i the table index for key's hash code
* @param e the entry at table[i]
* @return the entry associated with key, or null if no such
*/
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
ThreadLocal<?> k = e.get();
// 如果找到,則返回
if (k == key)
return e;
// 如果Entry對應的ThreadLocal被回收,通過expungeStaleEntry清理無效的Entry
if (k == null)
expungeStaleEntry(i);
else
// 獲取環形意義上的下一個索引
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
/**
* Expunge a stale entry by rehashing any possibly colliding entries
* lying between staleSlot and the next null slot. This also expunges
* any other stale entries encountered before the trailing null. See
* Knuth, Section 6.4
*
* @param staleSlot index of slot known to have null key
* @return the index of the next null slot after staleSlot
* (all between staleSlot and this slot will have been checked
* for expunging).
*/
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// expunge entry at staleSlot
// 設置要清理的位置value爲空
tab[staleSlot].value = null;
// 整個Entry置爲空,方便GC回收
tab[staleSlot] = null;
size--;
// Rehash until we encounter null
Entry e;
int i;
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;
// Unlike Knuth 6.4 Algorithm R, we must scan until
// null because multiple entries could have been stale.
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
四 ThreadLocal存在哪些問題
1、內存溢出問題(OOM)
ThreadLocal是Thread裏面維護的一個局部變量,線程不退出,局部變量的值就一直存在。正常情況下單個線程執行完就退出,
那麼就會做清理工作,但是當使用多線程的時候,如果我們在ThreadLocal存的是大對象,併發量高的時候,每個線程都持有大
對象,很容易造成內存溢出,ThreadLocal是以“時間換空間”。
五 ThreadLocal總結
1、ThreadLocal.ThreadLocalMap是Thread的一個局部變量;
2、ThreadLocalMap是類似Map的結構,最大的區別在於key,可以看爲是ThreadLocal對象實例的Hash值作爲Key來存儲value,
採用弱引用內存回收機制。
3、 ThreadLocal實現了線程與線程之間的隔離,因爲是線程的局部變量,自己有一份同時,也實現了每個ThreadLocal之間的
隔離,因爲key是ThreadLocal對象實例的Hash值,是不一樣的。
4、線程退出時,局部變量自動回收內存。
5、ThreaLocal是以空間換時間的做法,就是用內存空間存起來,維持到整個線程結束,但是,線程之間就不需要排隊訪問,
在自己的線程裏面隨心所欲。