Java中的ThreadLocal線程池原理
ThreadLocal
ThreadLocal提供了線程的局部變量(或本地變量)。
它可以保證訪問(wèn)到的變量屬于當(dāng)前線程,每個(gè)訪問(wèn)這種變量的線程(通過(guò)它的get或set方法)都有自己的、獨(dú)立初始化的變量副本,每個(gè)線程的變量都不同。
ThreadLocal相當(dāng)于提供了一種線程隔離,將變量與線程相綁定。
ThreadLocal類(lèi)定義如下:可以簡(jiǎn)單瞄一眼吆,畢竟沒(méi)有瀏覽的欲望....
public class ThreadLocal<T> {
private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode = new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
protected T initialValue() {
return null;
}
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
return new SuppliedThreadLocal<>(supplier);
}
public ThreadLocal() {
}
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();
}
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;
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
T childValue(T parentValue) {
throw new UnsupportedOperationException();
}
static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {
private final Supplier<? extends T> supplier;
SuppliedThreadLocal(Supplier<? extends T> supplier) {
this.supplier = Objects.requireNonNull(supplier);
}
@Override
protected T initialValue() {
return supplier.get();
}
}
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
private static final int INITIAL_CAPACITY = 16;
private Entry[] table;
private int size = 0;
private int threshold; // Default to 0
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
private static int prevIndex(int i, int len) {
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
Object value = key.childValue(e.value);
Entry c = new Entry(key, value);
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}
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);
}
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;
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
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();
}
private void remove(ThreadLocal<?> key) {
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)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
private void replaceStaleEntry(ThreadLocal<?> key, Object value,
int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e;
int slotToExpunge = staleSlot;
for (int i = prevIndex(staleSlot, len);
(e = tab[i]) != null;
i = prevIndex(i, len))
if (e.get() == null)
slotToExpunge = i;
for (int i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
tab[i] = tab[staleSlot];
tab[staleSlot] = e;
if (slotToExpunge == staleSlot)
slotToExpunge = i;
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
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;
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
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;
}
private void rehash() {
expungeStaleEntries();
if (size >= threshold - threshold / 4)
resize();
}
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 {
int h = k.threadLocalHashCode & (newLen - 1);
while (newTab[h] != null)
h = nextIndex(h, newLen);
newTab[h] = e;
count++;
}
}
}
setThreshold(newLen);
size = count;
table = newTab;
}
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);
}
}
}
}內(nèi)部方法:

ThreadLocal通過(guò)threadLocalHashCode來(lái)標(biāo)識(shí)每一個(gè)ThreadLocal的唯一性。threadLocalHashCode通過(guò)CAS操作進(jìn)行更新,每次hash操作的增量為 0x61c88647(不知為何)。
接下來(lái)看下ThreadLocal的set、get等相關(guā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);
}通過(guò)Thread.currentThread()方法獲取了當(dāng)前的線程引用,并傳給了getMap(Thread)方法獲取一個(gè)ThreadLocalMap的實(shí)例。我們繼續(xù)跟進(jìn)getMap(Thread)方法:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}可以看到getMap(Thread)方法直接返回Thread實(shí)例的成員變量threadLocals。它的定義在Thread內(nèi)部,訪問(wèn)級(jí)別為package級(jí)別:
public class Thread implements Runnable {
private static native void registerNatives();
static {
registerNatives();
}
private volatile char name[];
private int priority;
private Thread threadQ;
private long eetop;
private boolean single_step;
private boolean daemon = false;
private boolean stillborn = false;
private Runnable target;
private ThreadGroup group;
private ClassLoader contextClassLoader;
private AccessControlContext inheritedAccessControlContext;
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
return threadInitNumber++;
}
ThreadLocal.ThreadLocalMap threadLocals = null;
..........
}到了這里,我們可以看出,每個(gè)Thread里面都有一個(gè)ThreadLocal.ThreadLocalMap成員變量,也就是說(shuō)每個(gè)線程通過(guò)ThreadLocal.ThreadLocalMap與ThreadLocal相綁定,這樣可以確保每個(gè)線程訪問(wèn)到的thread-local variable都是本線程的。
我們往下繼續(xù)分析。獲取了ThreadLocalMap實(shí)例以后,如果它不為空則調(diào)用ThreadLocalMap.ThreadLocalMap 的set方法設(shè)值;若為空則調(diào)用ThreadLocal 的createMap方法new一個(gè)ThreadLocalMap實(shí)例并賦給Thread.threadLocals。
ThreadLocal 的 createMap方法的源碼如下:
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}而ThreadLocalMap是ThreadLocal的一個(gè)靜態(tài)內(nèi)部類(lèi),在文章開(kāi)頭貼出的ThreadLocal源碼可查看。
總結(jié):
set操作是向當(dāng)前線程的ThreadLocal.ThreadLocalMap類(lèi)型的成員變量threadLocals中設(shè)置值,key是this,value是我們指定的值
注意,這里傳的this代表的是那個(gè)ThreadLocal類(lèi)型的變量(或者說(shuō)叫對(duì)象)
也就是說(shuō),每個(gè)線程都維護(hù)了一個(gè)ThreadLocal.ThreadLocalMap類(lèi)型的對(duì)象,而set操作其實(shí)就是以ThreadLocal變量為key,以我們指定的值為value,最后將這個(gè)鍵值對(duì)封裝成Entry對(duì)象放到該線程的ThreadLocal.ThreadLocalMap對(duì)象中。每個(gè)ThreadLocal變量在該線程中都是ThreadLocal.ThreadLocalMap對(duì)象中的一個(gè)Entry。既然每個(gè)ThreadLocal變量都對(duì)應(yīng)ThreadLocal.ThreadLocalMap中的一個(gè)元素,那么就可以對(duì)這些元素進(jìn)行讀寫(xiě)刪除操作。
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();
}通過(guò)Thread.currentThread()方法獲取了當(dāng)前的線程引用,并傳給了getMap(Thread)方法獲取一個(gè)ThreadLocalMap的實(shí)例,getMap方法前面已經(jīng)貼出來(lái)了。繼續(xù)跟進(jìn)setInitialValue()方法:
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;
}首先調(diào)用 initialValue()方法來(lái)初始化,然后 通過(guò)Thread.currentThread()方法獲取了當(dāng)前的線程引用,并傳給了getMap(Thread)方法獲取一個(gè)ThreadLocalMap的實(shí)例,并將 初始化值存到ThreadLocalMap 中。
initialValue() 源碼如下:
protected T initialValue() {
return null;
}總結(jié):
get()方法就是從當(dāng)前線程的ThreadLocal.ThreadLocalMap對(duì)象中取出對(duì)應(yīng)的ThreadLocal變量所對(duì)應(yīng)的值
同理,remove()方法就是清除這個(gè)值
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}ThreadLocal的圖形理解:

或者

ThreadLocal的使用場(chǎng)景是在線程的聲明周期內(nèi)傳值(數(shù)據(jù)庫(kù)連接、session管理等),ThreadLocal關(guān)鍵點(diǎn)是在于ThreadLocalMap,可以說(shuō)一切歸功于此,看完上面的描述,應(yīng)該會(huì)有一個(gè)直觀的體會(huì)吧。
下面我們探究一下ThreadLocalMap的實(shí)現(xiàn)。
ThreadLocalMap
ThreadLocalMap是ThreadLocal的靜態(tài)內(nèi)部類(lèi),部分源碼如下:
public class ThreadLocal<T> {
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
/**
* The initial capacity -- MUST be a power of two.
*/
private static final int INITIAL_CAPACITY = 16;
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;
/**
* The number of entries in the table.
*/
private int size = 0;
/**
* The next size value at which to resize.
*/
private int threshold; // Default to 0
ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
}
}其中INITIAL_CAPACITY代表這個(gè)Map的初始容量;1是一個(gè)Entry類(lèi)型的數(shù)組,用于存儲(chǔ)數(shù)據(jù);size代表表中的存儲(chǔ)數(shù)目;threshold代表需要擴(kuò)容時(shí)對(duì)應(yīng)size的閾值。
Entry類(lèi)是ThreadLocalMap的靜態(tài)內(nèi)部類(lèi),用于存儲(chǔ)數(shù)據(jù)。
Entry類(lèi)繼承了WeakReference<ThreadLocal<?>>,即每個(gè)Entry對(duì)象都有一個(gè)ThreadLocal的弱引用(作為key),這是為了防止內(nèi)存泄露。一旦線程結(jié)束,key變?yōu)橐粋€(gè)不可達(dá)的對(duì)象,這個(gè)Entry就可以被GC了。
接下來(lái)我們來(lái)看ThreadLocalMap 的set方法的實(shí)現(xiàn):
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)]) {
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();
}ThreadLocal 的get方法會(huì)調(diào)用 ThreadLocalMap 的 getEntry(ThreadLocal key) ,其源碼如下:
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);
}
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;
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}ThreadLocal弱引用
說(shuō)ThreadLocal是一個(gè)弱引用,其本質(zhì)是ThreadLocal類(lèi)中ThreadLocalMap類(lèi)中的Entry的key是一個(gè)弱引用。前面提到過(guò)Entry中的key是this,this指向ThreadLocal
在ThreadLocal源碼中,截取一段ThreadLocalMap的源碼如下:
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
//由于Entry繼承了WeakReference,所以這里以一個(gè)弱引用指向ThreadLcoal對(duì)象
super(k);
value = v;
}
}為什么要這么做呢?
看下面的這種場(chǎng)景:
public void func1() {
ThreadLocal tl = new ThreadLocal<Integer>(); //line1
tl.set(100); //line2
tl.get(); //line3
}line1新建了一個(gè)ThreadLocal對(duì)象,t1 是強(qiáng)引用指向這個(gè)對(duì)象;line2調(diào)用set()后,新建一個(gè)Entry,通過(guò)源碼可知entry對(duì)象里的 k是弱引用指向這個(gè)對(duì)象。如圖:

當(dāng)func1方法執(zhí)行完畢后,棧幀銷(xiāo)毀,強(qiáng)引用 tl 也就沒(méi)有了,但此時(shí)線程的ThreadLocalMap里某個(gè)entry的 k 引用還指向這個(gè)對(duì)象。若這個(gè)k 引用是強(qiáng)引用,就會(huì)導(dǎo)致k指向的ThreadLocal對(duì)象及v指向的對(duì)象不能被gc回收,造成內(nèi)存泄漏,但是弱引用就不會(huì)有這個(gè)問(wèn)題(弱引用及強(qiáng)引用等這里不說(shuō)了)。使用弱引用,就可以使ThreadLocal對(duì)象在方法執(zhí)行完畢后順利被回收,而且在entry的k引用為null后,再調(diào)用get,set或remove方法時(shí),就會(huì)嘗試刪除key為null的entry,可以釋放value對(duì)象所占用的內(nèi)存。
概括說(shuō)就是:在方法中新建一個(gè)ThreadLocal對(duì)象,就有一個(gè)強(qiáng)引用指向它,在調(diào)用set()后,線程的ThreadLocalMap對(duì)象里的Entry對(duì)象又有一個(gè)引用 k 指向它。如果后面這個(gè)引用 k 是強(qiáng)引用就會(huì)使方法執(zhí)行完,棧幀中的強(qiáng)引用銷(xiāo)毀了,對(duì)象還不能回收,造成嚴(yán)重的內(nèi)存泄露。
注意:雖然弱引用,保證了k指向的ThreadLocal對(duì)象能被及時(shí)回收,但是v指向的value對(duì)象是需要ThreadLocalMap調(diào)用get、set時(shí)發(fā)現(xiàn)k為null時(shí)才會(huì)去回收整個(gè)entry、value,因此弱引用不能保證內(nèi)存完全不泄露。我們要在不使用某個(gè)ThreadLocal對(duì)象后,手動(dòng)調(diào)用remoev方法來(lái)刪除它,尤其是在線程池中,不僅僅是內(nèi)存泄露的問(wèn)題,因?yàn)榫€程池中的線程是重復(fù)使用的,意味著這個(gè)線程的ThreadLocalMap對(duì)象也是重復(fù)使用的,如果我們不手動(dòng)調(diào)用remove方法,那么后面的線程就有可能獲取到上個(gè)線程遺留下來(lái)的value值,造成bug。
到此這篇關(guān)于Java中的ThreadLocal線程池原理的文章就介紹到這了,更多相關(guān)ThreadLocal線程池內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaWeb之Servlet注冊(cè)頁(yè)面的實(shí)現(xiàn)示例
注冊(cè)頁(yè)面是很多網(wǎng)站都會(huì)是使用的到,本文主要介紹了JavaWeb之Servlet注冊(cè)頁(yè)面的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-04-04
Java17中record替代Lombok部分功能使用場(chǎng)景探究
這篇文章主要介紹了使用Java17中的record替代Lombok的部分功能,本文來(lái)為大家小小的總結(jié)下,我們可以在哪些地方,利用record來(lái)替換Lombok2024-01-01
詳述IntelliJ IDEA 中自動(dòng)生成 serialVersionUID 的方法(圖文)
本篇文章主要介紹了詳述IntelliJ IDEA 中自動(dòng)生成 serialVersionUID 的方法(圖文),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-11-11
Mybatis配置之typeAlias標(biāo)簽的用法
這篇文章主要介紹了Mybatis配置之typeAlias標(biāo)簽的用法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07
Java如何實(shí)現(xiàn)驗(yàn)證碼驗(yàn)證功能
這篇文章主要教大家如何實(shí)現(xiàn)Java驗(yàn)證碼驗(yàn)證功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-02-02
java中public class與class的區(qū)別詳解
以下是對(duì)java中public class與class的區(qū)別進(jìn)行了分析介紹,需要的朋友可以過(guò)來(lái)參考下2013-07-07
Java基礎(chǔ)類(lèi)庫(kù)之StringBuffer類(lèi)用法詳解
String類(lèi)是在所有開(kāi)發(fā)項(xiàng)目開(kāi)發(fā)之中一定會(huì)使用的一個(gè)功能類(lèi)。雖然String類(lèi)很好用,但也有弊端——內(nèi)容不允許頻繁修改,所以為了解決問(wèn)題,我們提供了StringBuffer類(lèi)。本文就來(lái)講講StringBuffer類(lèi)的用法2022-07-07
java中staticclass靜態(tài)類(lèi)詳解
這篇文章主要介紹了java中staticclass靜態(tài)類(lèi)詳解,具有一定借鑒價(jià)值,需要的朋友可以了解下。2017-12-12

