Java中的ThreadLocal線程池原理
ThreadLocal
ThreadLocal提供了線程的局部變量(或本地變量)。
它可以保證訪問到的變量屬于當(dāng)前線程,每個訪問這種變量的線程(通過它的get或set方法)都有自己的、獨立初始化的變量副本,每個線程的變量都不同。
ThreadLocal相當(dāng)于提供了一種線程隔離,將變量與線程相綁定。
ThreadLocal類定義如下:可以簡單瞄一眼吆,畢竟沒有瀏覽的欲望....
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通過threadLocalHashCode來標(biāo)識每一個ThreadLocal的唯一性。threadLocalHashCode通過CAS操作進(jìn)行更新,每次hash操作的增量為 0x61c88647(不知為何)。
接下來看下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); }
通過Thread.currentThread()方法獲取了當(dāng)前的線程引用,并傳給了getMap(Thread)方法獲取一個ThreadLocalMap的實例。我們繼續(xù)跟進(jìn)getMap(Thread)方法:
ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
可以看到getMap(Thread)方法直接返回Thread實例的成員變量threadLocals。它的定義在Thread內(nèi)部,訪問級別為package級別:
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; .......... }
到了這里,我們可以看出,每個Thread里面都有一個ThreadLocal.ThreadLocalMap成員變量,也就是說每個線程通過ThreadLocal.ThreadLocalMap與ThreadLocal相綁定,這樣可以確保每個線程訪問到的thread-local variable都是本線程的。
我們往下繼續(xù)分析。獲取了ThreadLocalMap實例以后,如果它不為空則調(diào)用ThreadLocalMap.ThreadLocalMap 的set方法設(shè)值;若為空則調(diào)用ThreadLocal 的createMap方法new一個ThreadLocalMap實例并賦給Thread.threadLocals。
ThreadLocal 的 createMap方法的源碼如下:
void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
而ThreadLocalMap是ThreadLocal的一個靜態(tài)內(nèi)部類,在文章開頭貼出的ThreadLocal源碼可查看。
總結(jié):
set操作是向當(dāng)前線程的ThreadLocal.ThreadLocalMap類型的成員變量threadLocals中設(shè)置值,key是this,value是我們指定的值
注意,這里傳的this代表的是那個ThreadLocal類型的變量(或者說叫對象)
也就是說,每個線程都維護(hù)了一個ThreadLocal.ThreadLocalMap類型的對象,而set操作其實就是以ThreadLocal變量為key,以我們指定的值為value,最后將這個鍵值對封裝成Entry對象放到該線程的ThreadLocal.ThreadLocalMap對象中。每個ThreadLocal變量在該線程中都是ThreadLocal.ThreadLocalMap對象中的一個Entry。既然每個ThreadLocal變量都對應(yīng)ThreadLocal.ThreadLocalMap中的一個元素,那么就可以對這些元素進(jìn)行讀寫刪除操作。
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(); }
通過Thread.currentThread()方法獲取了當(dāng)前的線程引用,并傳給了getMap(Thread)方法獲取一個ThreadLocalMap的實例,getMap方法前面已經(jīng)貼出來了。繼續(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()方法來初始化,然后 通過Thread.currentThread()方法獲取了當(dāng)前的線程引用,并傳給了getMap(Thread)方法獲取一個ThreadLocalMap的實例,并將 初始化值存到ThreadLocalMap 中。
initialValue() 源碼如下:
protected T initialValue() { return null; }
總結(jié):
get()方法就是從當(dāng)前線程的ThreadLocal.ThreadLocalMap對象中取出對應(yīng)的ThreadLocal變量所對應(yīng)的值
同理,remove()方法就是清除這個值
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
ThreadLocal的圖形理解:
或者
ThreadLocal的使用場景是在線程的聲明周期內(nèi)傳值(數(shù)據(jù)庫連接、session管理等),ThreadLocal關(guān)鍵點是在于ThreadLocalMap,可以說一切歸功于此,看完上面的描述,應(yīng)該會有一個直觀的體會吧。
下面我們探究一下ThreadLocalMap的實現(xiàn)。
ThreadLocalMap
ThreadLocalMap是ThreadLocal的靜態(tài)內(nè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代表這個Map的初始容量;1是一個Entry類型的數(shù)組,用于存儲數(shù)據(jù);size代表表中的存儲數(shù)目;threshold代表需要擴(kuò)容時對應(yīng)size的閾值。
Entry類是ThreadLocalMap的靜態(tài)內(nèi)部類,用于存儲數(shù)據(jù)。
Entry類繼承了WeakReference<ThreadLocal<?>>,即每個Entry對象都有一個ThreadLocal的弱引用(作為key),這是為了防止內(nèi)存泄露。一旦線程結(jié)束,key變?yōu)橐粋€不可達(dá)的對象,這個Entry就可以被GC了。
接下來我們來看ThreadLocalMap 的set方法的實現(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方法會調(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弱引用
說ThreadLocal是一個弱引用,其本質(zhì)是ThreadLocal類中ThreadLocalMap類中的Entry的key是一個弱引用。前面提到過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,所以這里以一個弱引用指向ThreadLcoal對象 super(k); value = v; } }
為什么要這么做呢?
看下面的這種場景:
public void func1() { ThreadLocal tl = new ThreadLocal<Integer>(); //line1 tl.set(100); //line2 tl.get(); //line3 }
line1新建了一個ThreadLocal對象,t1 是強引用指向這個對象;line2調(diào)用set()后,新建一個Entry,通過源碼可知entry對象里的 k是弱引用指向這個對象。如圖:
當(dāng)func1方法執(zhí)行完畢后,棧幀銷毀,強引用 tl 也就沒有了,但此時線程的ThreadLocalMap里某個entry的 k 引用還指向這個對象。若這個k 引用是強引用,就會導(dǎo)致k指向的ThreadLocal對象及v指向的對象不能被gc回收,造成內(nèi)存泄漏,但是弱引用就不會有這個問題(弱引用及強引用等這里不說了)。使用弱引用,就可以使ThreadLocal對象在方法執(zhí)行完畢后順利被回收,而且在entry的k引用為null后,再調(diào)用get,set或remove方法時,就會嘗試刪除key為null的entry,可以釋放value對象所占用的內(nèi)存。
概括說就是:在方法中新建一個ThreadLocal對象,就有一個強引用指向它,在調(diào)用set()后,線程的ThreadLocalMap對象里的Entry對象又有一個引用 k 指向它。如果后面這個引用 k 是強引用就會使方法執(zhí)行完,棧幀中的強引用銷毀了,對象還不能回收,造成嚴(yán)重的內(nèi)存泄露。
注意:雖然弱引用,保證了k指向的ThreadLocal對象能被及時回收,但是v指向的value對象是需要ThreadLocalMap調(diào)用get、set時發(fā)現(xiàn)k為null時才會去回收整個entry、value,因此弱引用不能保證內(nèi)存完全不泄露。我們要在不使用某個ThreadLocal對象后,手動調(diào)用remoev方法來刪除它,尤其是在線程池中,不僅僅是內(nèi)存泄露的問題,因為線程池中的線程是重復(fù)使用的,意味著這個線程的ThreadLocalMap對象也是重復(fù)使用的,如果我們不手動調(diào)用remove方法,那么后面的線程就有可能獲取到上個線程遺留下來的value值,造成bug。
到此這篇關(guān)于Java中的ThreadLocal線程池原理的文章就介紹到這了,更多相關(guān)ThreadLocal線程池內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaWeb之Servlet注冊頁面的實現(xiàn)示例
注冊頁面是很多網(wǎng)站都會是使用的到,本文主要介紹了JavaWeb之Servlet注冊頁面的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-04-04Java17中record替代Lombok部分功能使用場景探究
這篇文章主要介紹了使用Java17中的record替代Lombok的部分功能,本文來為大家小小的總結(jié)下,我們可以在哪些地方,利用record來替換Lombok2024-01-01詳述IntelliJ IDEA 中自動生成 serialVersionUID 的方法(圖文)
本篇文章主要介紹了詳述IntelliJ IDEA 中自動生成 serialVersionUID 的方法(圖文),具有一定的參考價值,感興趣的小伙伴們可以參考一下。2017-11-11Mybatis配置之typeAlias標(biāo)簽的用法
這篇文章主要介紹了Mybatis配置之typeAlias標(biāo)簽的用法,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07java中public class與class的區(qū)別詳解
以下是對java中public class與class的區(qū)別進(jìn)行了分析介紹,需要的朋友可以過來參考下2013-07-07Java基礎(chǔ)類庫之StringBuffer類用法詳解
String類是在所有開發(fā)項目開發(fā)之中一定會使用的一個功能類。雖然String類很好用,但也有弊端——內(nèi)容不允許頻繁修改,所以為了解決問題,我們提供了StringBuffer類。本文就來講講StringBuffer類的用法2022-07-07