摘要:零前期準備文章異常啰嗦且繞彎。二是底層真正起作用的類,并且提供了大量的靜態方法。在普通的線程中,這個對象由于本身沒有的原生支持,所以只能附著在對象當中。同一個線程中如果創建多個對象,獲取到的是同一個。
零 前期準備 0 FBI WARNING
文章異常啰嗦且繞彎。
1 版本JDK 版本 : OpenJDK 11.0.1
IDE : idea 2018.3
Netty 版本 : netty-all 4.1.34.Final
2 FastThreadLocal 簡介FastThreadLocal 是 Netty 中實現的高性能 ThreadLocal 工具,功能上和 ThreadLocal 差不多,但是性能上遠高于 jdk 自帶的 ThreadLocal。
3 Demoimport io.netty.util.concurrent.FastThreadLocal; public class FastThreadLocalDemo { public static void main(String[] args) { //創建 FastThreadLocal 對象 FastThreadLocal一 FastThreadLocal 的創建tl = new FastThreadLocal<>(); //FastThreadLocal 的存入功能 long setBegin = System.nanoTime(); tl.set("test"); long setAfter = System.nanoTime(); System.out.println("get : " + (setAfter - setBegin)); //FastThreadLocal 的獲取功能 long getBegin = System.nanoTime(); String fastGet = tl.get(); long getAfter = System.nanoTime(); System.out.println("get : " + (getAfter - getBegin)); //FastThreadLocal 的移除功能 long removeBegin = System.nanoTime(); tl.remove(); long removeAfter = System.nanoTime(); System.out.println("remove : " + (removeAfter - removeBegin)); } }
回到 Demo 中的創建代碼:
FastThreadLocaltl = new FastThreadLocal<>();
追蹤 FastThreadLocal 的構造器:
//step 1 //FastThreadLocal.class public FastThreadLocal() { //index 是一個 int 類型的變量 index = InternalThreadLocalMap.nextVariableIndex(); } //step 2 //InternalThreadLocalMap.class public static int nextVariableIndex() { //nextIndex 是一個定義在 UnpaddedInternalThreadLocalMap 類中的靜態 AtomicInteger 類型對象 //UnpaddedInternalThreadLocalMap 是 InternalThreadLocalMap 的父類 //這里使用自增操作獲取一個 int 值,并返回 int index = nextIndex.getAndIncrement(); if (index < 0) { nextIndex.decrementAndGet(); throw new IllegalStateException("too many thread-local indexed variables"); } return index; }
其實 FastThreadLocal 的創建就只是獲取一個唯一的 int 值作為標識,沒有其它操作了。
二 InternalThreadLocalMapInternalThreadLocalMap 是 FastThreadLocal 底層真正起作用的 ThreadLocal 類,并且提供了大量的靜態方法。
1 獲取對象最為核心的是 InternalThreadLocalMap 的 get() 方法:
//InternalThreadLocalMap.class public static InternalThreadLocalMap get() { //獲取當前線程的線程對象 Thread thread = Thread.currentThread(); //判斷線程對象的類型 if (thread instanceof FastThreadLocalThread) { //在 Netty 中使用的 FastThreadLocal 的時候會用到該類型的方法 return fastGet((FastThreadLocalThread) thread); } else { //正常情況下多帶帶使用 FastThreadLocal,線程對象不會是 FastThreadLocalThread return slowGet(); } }
先來看 fastGet():
//InternalThreadLocalMap.class private static InternalThreadLocalMap fastGet(FastThreadLocalThread thread) { //直接獲取 FastThreadLocalThread 中的 threadLocalMap 對象并返回即可 InternalThreadLocalMap threadLocalMap = thread.threadLocalMap(); //為空的話新建一個 if (threadLocalMap == null) { thread.setThreadLocalMap(threadLocalMap = new InternalThreadLocalMap()); } return threadLocalMap; }
再來看 slowGet():
//InternalThreadLocalMap.class private static InternalThreadLocalMap slowGet() { //獲取 UnpaddedInternalThreadLocalMap 中的 slowThreadLocalMap 對象 //slowThreadLocalMap 是一個靜態的 ThreadLocal 類型對象,儲存的數據類型是 InternalThreadLocalMap ThreadLocalslowThreadLocalMap = UnpaddedInternalThreadLocalMap.slowThreadLocalMap; //獲取該對象中的 InternalThreadLocalMap 對象實例并返回 InternalThreadLocalMap ret = slowThreadLocalMap.get(); //為空的情況下新建一個 if (ret == null) { ret = new InternalThreadLocalMap(); slowThreadLocalMap.set(ret); } return ret; }
可以看到實際上對于 FastThreadLocal 來說,真正起作用的是 InternalThreadLocalMap 對象。
在普通的線程中,這個對象由于本身沒有 jdk 的原生支持,所以只能附著在 ThreadLocal 對象當中。
但是由于 UnpaddedInternalThreadLocalMap 中的 slowThreadLocalMap 本身是一個靜態的 ThreadLocal 對象,所以不同的線程實際上調用到的都是同一個對象,但是獲取到的 InternalThreadLocalMap 卻不是同一個。同一個線程中如果創建多個 FastThreadLocal 對象,獲取到的是同一個 InternalThreadLocalMap。
2 setInternalThreadLocalMap 的底層儲存是一個 Object 數組,通過 setIndexedVariable(...) 方法儲存進去:
//InternalThreadLocalMap.class public boolean setIndexedVariable(int index, Object value) { //indexedVariables 是一個定義在 UnpaddedInternalThreadLocalMap 中的 Object 數組 Object[] lookup = indexedVariables; //在這里需要判斷數組的長度問題 //index 是每個 FastThreadLocal 創建的時候都會獲取的唯一標識碼,同時也是數組上的位置 if (index < lookup.length) { //獲取原值 Object oldValue = lookup[index]; //賦值 lookup[index] = value; //UNSET 是一個靜態的 Object 對象,用于默認填充 lookup 數組 //此處 oldValue 如果等于 UNSET,則證明該位置上原來不存在對象儲存 //如果是已經儲存過對象,又調用該方法替換了一次,會返回 false return oldValue == UNSET; } else { //數組擴容 expandIndexedVariableTableAndSet(index, value); return true; } }3 get
InternalThreadLocalMap 中獲取值的方法是通過 indexedVariable(...) 方法:
//InternalThreadLocalMap.class public Object indexedVariable(int index) { //根據 index 從數組中獲取到想要的位置的值 Object[] lookup = indexedVariables; return index < lookup.length? lookup[index] : UNSET; }4 remove
InternalThreadLocalMap 中刪除值的方法是通過 indexedVariable(...) 方法:
//InternalThreadLocalMap.class public Object removeIndexedVariable(int index) { //獲取數組 Object[] lookup = indexedVariables; if (index < lookup.length) { Object v = lookup[index]; //將指定位置的值替換成 UNSET 對象 lookup[index] = UNSET; return v; } else { return UNSET; } }
InternalThreadLocalMap 還有一個靜態的 remove() 方法用于清除自身:
//InternalThreadLocalMap.class public static void remove() { Thread thread = Thread.currentThread(); if (thread instanceof FastThreadLocalThread) { ((FastThreadLocalThread) thread).setThreadLocalMap(null); } else { slowThreadLocalMap.remove(); } }
代碼比較簡單,不做過多分析。
二 存入元素回到 Demo 中的存入元素的代碼:
tl.set("test");
追蹤 get(...) 方法:
//step 1 //FastThreadLocal.class public final void set(V value) { //先判斷 value 不是 UNSET 對象 if (value != InternalThreadLocalMap.UNSET) { //獲取 InternalThreadLocalMap 對象 InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get(); //setKnownNotUnset(...) 方法會將 value 存入 threadLocalMap 中 if (setKnownNotUnset(threadLocalMap, value)) { //此處會清理已經被 gc 回收的線程對象所儲存的值 registerCleaner(threadLocalMap); } } else { remove(); } } //step 2 //FastThreadLocal.class private boolean setKnownNotUnset(InternalThreadLocalMap threadLocalMap, V value) { //調用 setIndexedVariable(...) 方法去存儲 value,具體見上方 InternalThreadLocalMap 的詳細解讀 if (threadLocalMap.setIndexedVariable(index, value)) { //addToVariablesToRemove(...) 方法會將 FastThreadLocal 對象存放到 threadLocalMap 中的一個集合中 //這個集合用于在需要的時候集中銷毀 FastThreadLocal addToVariablesToRemove(threadLocalMap, this); return true; } return false; }三 獲取元素
回到 Demo 中獲取元素的代碼:
String fastGet = tl.get();
追蹤 set(...) 方法:
//FastThreadLocal.class public final V get() { //獲取 InternalThreadLocalMap 對象 InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get(); //從 InternalThreadLocalMap 中獲取值 Object v = threadLocalMap.indexedVariable(index); //如果存在值,則直接返回該值即可 if (v != InternalThreadLocalMap.UNSET) { return (V) v; } //不存在的情況下,initialize(...) 會返回一個 null 值 V value = initialize(threadLocalMap); //gc 處理 registerCleaner(threadLocalMap); //返回 null return value; }三 移除元素
回到 Demo 中移除元素的代碼:
String fastGet = tl.get();
追蹤 remove(...) 方法:
//step 1 //FastThreadLocal.class public final void remove() { //InternalThreadLocalMap 的 getIfSet() 方法會獲取 InternalThreadLocalMap 對象 remove(InternalThreadLocalMap.getIfSet()); } //step 2 //FastThreadLocal.class public final void remove(InternalThreadLocalMap threadLocalMap) { //有效性驗證 if (threadLocalMap == null) { return; } //清除值 Object v = threadLocalMap.removeIndexedVariable(index); //到之前 threadLocalMap 中保存 FastThreadLocal 對象的集合里去刪除對象 removeFromVariablesToRemove(threadLocalMap, this); if (v != InternalThreadLocalMap.UNSET) { try { //此方法為空,是預留的一個處理方法,使用者也可以自己做實現 onRemoval((V) v); } catch (Exception e) { PlatformDependent.throwException(e); } } }四 內存管理
在真實的開發環境中,可能會存在一個線程使用了此 FastThreadLocal,然后線程完成之后被 gc 回收了,但是該 FastThreadLocal 的值沒有被回收的情況。
所以在 FastThreadLocal 中就由一個防止內存泄漏的方法 registerCleaner(...):
//FastThreadLocal.class private void registerCleaner(final InternalThreadLocalMap threadLocalMap) { Thread current = Thread.currentThread(); //如果 FastThreadLocalThread 被標記為要被清理,或者 index 這個位置的元素并不被收錄于清理目錄下,則直接返回 if (FastThreadLocalThread.willCleanupFastThreadLocals(current) || threadLocalMap.isCleanerFlagSet(index)) { return; } //將 index 收錄到清理目錄中 threadLocalMap.setCleanerFlag(index); //下方代碼是防止內存泄漏的核心代碼,但是已經被注釋掉了 //值得一提的是,在以前的 Netty 版本中是存在的,但是在筆者追蹤的 4.1.34 版本中被注釋掉了 //根據解釋,是官方覺得這種處理方式不夠優雅,所以暫時將此段代碼注釋掉了,并且打上了 TODO 字樣 // ObjectCleaner.register(current, new Runnable() { // @Override // public void run() { // remove(threadLocalMap); // } // }); }五 一點嘮叨
FastThreadLocal 對比 jdk 的原生 ThreadLocal,性能優勢主要表現在以下幾個方面:
1、Netty 基于自己的業務需求,對線程對象進行了封裝,并在此過程中內嵌了對 FastThreadLocal 的支持 2、FastThreadLocal 中省略了 ThreadLocal 中的節點對象的組裝和 Hash 值的計算過程,結構更加簡單,存、拿過程的效率更高 3、ThreadLocal 對于內存的控制比 FastThreadLocal 更加嚴謹,消耗更多的精力去進行內存檢查和清理 4、FastThreadLocal 中靜態(static)方法的使用更加頻繁,是典型的以空間換時間的做法
本文僅為個人的學習筆記,可能存在錯誤或者表述不清的地方,有緣補充
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/73782.html
摘要:雖然類名中帶有字樣,但是實際上并不是接口的子類。是弱連接接口,這意味著如果僅有指向某一類,其任然有可能被回收掉。這里使用弱連接的意義,是為了防止業務代碼中置空對象,但是由于存在連接可達,所以仍然無法回收掉該對象的情況發生。 零 前期準備 0 FBI WARNING 文章異常啰嗦且繞彎。 1 版本 JDK 版本 : OpenJDK 11.0.1 IDE : idea 2018.3 2 T...
摘要:實現原理淺談幫助理解的示意圖中有一屬性,類型是的靜態內部類。剛剛說過,是一個中的靜態內部類,則是的內部節點。這個會在線程中,作為其屬性初始是一個數組的索引,達成與類似的效果。的方法被調用時,會根據記錄的槽位信息進行大掃除。 概述 FastThreadLocal的類名本身就充滿了對ThreadLocal的挑釁,快男FastThreadLocal是怎么快的?源碼中類注釋坦白如下: /** ...
摘要:容易導致內存泄漏。如果我們的強引用不存在的話,那么就會被回收,也就是會出現我們沒被回收,被回收,導致永遠存在,出現內存泄漏。緩存行和一次定位,不會有沖突由于使用數組,不會出現回收,沒被回收的尷尬局面,所以避免了內存泄漏。 1 背景 某一天在某一個群里面的某個群友突然提出了一個問題:threadlocal的key是虛引用,那么在threadlocal.get()的時候,發生GC之后,ke...
摘要:非阻塞模型這種也很好理解,由阻塞的死等系統響應進化成多次調用查看數據就緒狀態。復用模型,以及它的增強版就屬于該種模型。此時用戶進程阻塞在事件上,數據就緒系統予以通知。信號驅動模型應用進程建立信號處理程序時,是非阻塞的。 引言 之前的兩篇文章 FastThreadLocal怎么Fast?、ScheduledThreadPoolExecutor源碼解讀 搞的我心力交瘁,且讀源碼過程中深感功...
閱讀 2642·2019-08-30 15:52
閱讀 3589·2019-08-29 17:02
閱讀 1835·2019-08-29 13:00
閱讀 910·2019-08-29 11:07
閱讀 3228·2019-08-27 10:53
閱讀 1762·2019-08-26 13:43
閱讀 1004·2019-08-26 10:22
閱讀 1307·2019-08-23 18:06