上節講過了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的錯亂和洩漏問題。