Netty原始碼分析-- FastThreadLocal分析(十)

Diligent_Watermelon發表於2019-08-05

      上節講過了ThreadLocal的原始碼,這一節我們來看下FastThreadLocal。這個我覺得要比ThreadLocal要簡單,因為缺少了對於Entry的清理和整理工作,所以ThreadLocal的效率更高。

      跟ThreadLocal一樣,我們也先給一個結構圖:

      

      大家看這個圖跟ThreadLocal有哪些區別,首先是用一個Object陣列來替代了Entry陣列,不再是key鍵值對的形式。 另外Object[0]儲存一個Set<FastThreadLocal<?>>集合。

      OK,看完這個,原始碼就很好理解了,我們還是先看下 InternalThreadLocalMap 建構函式

1     private InternalThreadLocalMap() {
2         super(newIndexedVariableTable());
3     }
4 
5     private static Object[] newIndexedVariableTable() {
6         Object[] array = new Object[32]; //初始化 32 長度的Object陣列
7         Arrays.fill(array, UNSET);  // 將每個元素初始化成UNSET 這裡的UNSET 可以理解為佔位符, 因為null會被認為成有效值
8         return array;
9     }

10    UnpaddedInternalThreadLocalMap(Object[] indexedVariables) {
11 this.indexedVariables = indexedVariables; // 將建立的array賦給 Object[] indexedVariables;
12 }

     看完這個,我們來看下陣列的索引值是什麼設定的,開啟  FastThreadLocal, 看下全域性變數。

1 private static final int variablesToRemoveIndex = InternalThreadLocalMap.nextVariableIndex();

    上面這個瞭解類的載入機制的應該很清楚 variablesToRemoveIndex  初始化的時機。想要了解類的載入過程的 請移步 類的載入機制(一)

 1     static final AtomicInteger nextIndex = new AtomicInteger(); // 原子類
 2 
 3     public static int nextVariableIndex() {
 4         int index = nextIndex.getAndIncrement(); // 先獲取值,在增加,所以index = 0 
 5         if (index < 0) {
 6             nextIndex.decrementAndGet();
 7             throw new IllegalStateException("too many thread-local indexed variables");
 8         }
 9         return index;
10     }

       由此得出,variablesToRemoveIndex  = 0 固定值。再看下 FastThreadLocal 的構造方法

1     public FastThreadLocal() {
2         index = InternalThreadLocalMap.nextVariableIndex();
3     }

      大家看到這個,是不是就想到了,也就是說每個 FastThreadLocal 都有一個唯一的 index 值 , 那麼跟ThreadLocal相比的話,ThreadLocal要 先獲取一個hash值,然後再根據Entry陣列的長度進行運算得到一個索引值,所以說這樣也是Netty的這個FastThreadLocal效率更高的原因之一。

     為了更好地講下面的內容,我們再看一個 FastThreadLocalThread 

     繼承了Thread,增加了成員變數 InternalThreadLocalMap threadLocalMap.  不再是放在Thread中的成員變數了,看到這個想到了什麼? 那麼也就是說獲取某個執行緒儲存Object陣列結構的Map,是從FastThreadLocalThread中獲取。

     好了介紹完上面,看set方法

 1     public final void set(V value) {
 2         if (value != InternalThreadLocalMap.UNSET) { // 如果不是UNSET
 3             InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get(); // 獲取map
 4             if (setKnownNotUnset(threadLocalMap, value)) { // 新增或者更新值
 5                 registerCleaner(threadLocalMap); // 如果返回true, 代表新增, 設定清理位
 6             }
 7         } else { 
 8             remove(); // 如果入參是一個UNSET,那麼執行刪除邏輯
 9         }
10     }

     看下 InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();

1     public static InternalThreadLocalMap get() {
2         Thread thread = Thread.currentThread();
3         if (thread instanceof FastThreadLocalThread) { // 如果當前執行緒是一個FastThreadLocalThread
4             return fastGet((FastThreadLocalThread) thread); // 執行fastGet 這個名字是很有意思。。。。
5         } else {
6             return slowGet(); // 要不然就slowGet() Netty叫ThreadLocal  Slow。。。
7         }
8     }
1     private static InternalThreadLocalMap fastGet(FastThreadLocalThread thread) { // 如果當前的map沒有設定,則新建立一個
2         InternalThreadLocalMap threadLocalMap = thread.threadLocalMap();  // 這裡就是獲取了 FastThreadLocalThread 中的成員變數 threadLocalMap
3         if (threadLocalMap == null) {
4             thread.setThreadLocalMap(threadLocalMap = new InternalThreadLocalMap());
5         }
6         return threadLocalMap;
7     }

     看下新增或者更新的邏輯

1     private boolean setKnownNotUnset(InternalThreadLocalMap threadLocalMap, V value) {
2         if (threadLocalMap.setIndexedVariable(index, value)) { // index 是當前FastThreadLocal的索引 ,如果是 新增,那麼這個方法返回true ,如果是修改則返回 false
3             addToVariablesToRemove(threadLocalMap, this); // 新增的話,需要將當前的這個FastThreadLocal新增到Set<FashThreadLocal>集合中
4             return true; // 新增返回trye
5         }
6         return false; // 更新返回false
7     }
 1     public boolean setIndexedVariable(int index, Object value) {
 2         Object[] lookup = indexedVariables; // 當前的Object[]物件
 3         if (index < lookup.length) { // 檢查當前的索引是否超過了Object陣列的長度
 4             Object oldValue = lookup[index]; // 獲取當前的值
 5             lookup[index] = value; // 將新值設定進去
 6             return oldValue == UNSET; // 判斷之前的值是不是佔位符,如果是則代表新增,不是代表更新
 7         } else {
 8             expandIndexedVariableTableAndSet(index, value); // 如果超過了,則需要進行擴容
 9             return true;
10         }
11     }
 1     private void expandIndexedVariableTableAndSet(int index, Object value) { // 擴容邏輯
 2         Object[] oldArray = indexedVariables; // 當前的Object陣列
 3         final int oldCapacity = oldArray.length; // 當前的長度
 4         int newCapacity = index;  // index是這個FastThreadLocal對應的索引值
 5         newCapacity |= newCapacity >>>  1;  
 6         newCapacity |= newCapacity >>>  2;
 7         newCapacity |= newCapacity >>>  4;
 8         newCapacity |= newCapacity >>>  8;
 9         newCapacity |= newCapacity >>> 16;
10         newCapacity ++;       // 這段其實也很有意思,其實是為了計算新的陣列的容量,會變成下一個檔位的2的次方大小,比如 1->2 2->4 3->4 4->8 5->8 6->8 7->8 8->16 18->32, 如果不理解,
// 可以自己寫一個main方法試一下,後面我們在講Netty記憶體模型的時候大家也會看到這麼一段。
11 12 Object[] newArray = Arrays.copyOf(oldArray, newCapacity); // 將老的資料往新的陣列進行拷貝 13 Arrays.fill(newArray, oldCapacity, newArray.length, UNSET); // 將新的多出來的部分,設定佔位符 14 newArray[index] = value; // 將index對應的值設定完 15 indexedVariables = newArray; // 將新的陣列提升成Map中的indexedVariables 16 }

      接下來看下set中的這句 addToVariablesToRemove(threadLocalMap, this);  根據上面的結構,新增了value,那麼就需要修改set集合

 1     private static void addToVariablesToRemove(InternalThreadLocalMap threadLocalMap, FastThreadLocal<?> variable) {
 2         Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex); // 根據上面的分析 variablesToRemoveIndex = 0 獲取第0個位置的Object 然後看是不是null或者佔位符
 3         Set<FastThreadLocal<?>> variablesToRemove;
 4         if (v == InternalThreadLocalMap.UNSET || v == null) { // 如果是佔位符
 5             variablesToRemove = Collections.newSetFromMap(new IdentityHashMap<FastThreadLocal<?>, Boolean>());
 6             threadLocalMap.setIndexedVariable(variablesToRemoveIndex, variablesToRemove); // 建立一個set集合放到第0的位置上
 7         } else {
 8             variablesToRemove = (Set<FastThreadLocal<?>>) v; // 已經有了,則直接取過來
 9         }
10 
11         variablesToRemove.add(variable); // 新增新的FastThreadLocal到set集合
12     }

      接下來看get方法

 1     public final V get() {
 2         InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get(); // 獲取map
 3         Object v = threadLocalMap.indexedVariable(index); // 根據當前的Thread的索引,獲取Object
 4         if (v != InternalThreadLocalMap.UNSET) { // 不是佔位符直接返回
 5             return (V) v;
 6         }
 7 
 8         V value = initialize(threadLocalMap); // 如果是,則呼叫初始化方法
 9         registerCleaner(threadLocalMap);
10         return value;
11     }
 1     private V initialize(InternalThreadLocalMap threadLocalMap) {
 2         V v = null;
 3         try {
 4             v = initialValue(); // 空方法,供子類呼叫
 5         } catch (Exception e) {
 6             PlatformDependent.throwException(e);
 7         }
 8 
 9         threadLocalMap.setIndexedVariable(index, v); // 設定初始化的值
10         addToVariablesToRemove(threadLocalMap, this);  // 新增set集合
11         return v;
12     }

       接下來看remove方法

 1     public final void remove(InternalThreadLocalMap threadLocalMap) {
 2         if (threadLocalMap == null) {
 3             return;
 4         }
 5 
 6         Object v = threadLocalMap.removeIndexedVariable(index); // 根據當前的Thread的索引,獲取Object
 7         removeFromVariablesToRemove(threadLocalMap, this); // 移除set集合中的元素,這裡就不展開說了,上面懂了就很簡單了
 8 
 9         if (v != InternalThreadLocalMap.UNSET) {
10             try {
11                 onRemoval((V) v); // 空方法,供子類呼叫
12             } catch (Exception e) {
13                 PlatformDependent.throwException(e);
14             }
15         }
16     }

 好了,這就算是講完了,當然FastThreadLocal 不會整理資料和清除過期資料,是怎麼防止記憶體洩露的呢?

 看下 FastThreadLocalRunnable

 1 public class FastThreadLocalRunnable implements Runnable {
 2     private Runnable runnable;
 3 
 4     public FastThreadLocalRunnable(Runnable runnable) {
 5         this.runnable = ObjectUtil.checkNotNull(runnable, "runnable");
 6     }
 7 
 8     public static Runnable wrap(Runnable runnable) {
 9         return runnable instanceof FastThreadLocalRunnable ? runnable : new FastThreadLocalRunnable(runnable);
10     }
11 
12     @Override
13     public void run() {
14         try {
15             // 執行任務
16             this.runnable.run();
17         } finally {
18             /**
19              * 執行緒池中的執行緒由於會被複用,所以執行緒池中的每一條執行緒在執行task結束後,要清理掉其InternalThreadLocalMap和其內的FastThreadLocal資訊,
20              * 否則,當這條執行緒在下一次被複用的時候,其ThreadLocalMap資訊還儲存著上一次被使用時的資訊;
21              * 另外,假設這條執行緒不再被使用,但是這個執行緒有可能不會被銷燬(與執行緒池的型別和配置相關),那麼其上的ThreadLocal將發生資源洩露。
22              */
23             FastThreadLocal.removeAll();
24         }
25     }
26 }

        使用該類將一個普通的Runnable物件進行wrap裝飾,之後在呼叫FastThreadLocalRunnable.run()的時候,實際上會呼叫真實物件(即普通的Runnable物件)的run(),執行完成之後,會進行對當前執行緒的全量回收操作(刪除當前執行緒上的InternalThreadLocalMap中的每一個value以及threadLocalMap本身),這樣就可以有效的線上程池中複用當前執行緒而不必關心ftl的錯亂和洩漏問題。

相關文章