AndroidStudio 抓包工具Profiler使用
date: 2020-08-19 10:16:00
updated: 2020-08-19 10:16:00
ThreadLocal
以資料庫連線為例,如果多個執行緒共享一個連線,有可能一個執行緒在對資料庫進行操作,另一個執行緒呼叫了closeConnection操作;如果在每一個執行緒都new一個連線物件,如果開啟關閉資料庫操作頻繁,會影響到伺服器壓力,並且影響程式執行效能。
=> ThreadLocal 內部維護一個 ThreadLocalMap 類,儲存的是 Entry<Thread K, Object V> 陣列,K是執行緒,V是值,這樣每一個執行緒無論在哪裡呼叫,都會拿到自己執行緒的值
每個Thread物件中都持有一個ThreadLocalMap的成員變數。每個ThreadLocalMap內部又維護了N個Entry節點,也就是Entry陣列,每個Entry代表一個完整的物件,key是ThreadLocal本身,value是ThreadLocal的泛型值
ThreadLocalMap的引用是在Thread裡的,所以它裡面的Entry陣列存放的是一個執行緒裡new出來的多個ThreadLocal物件
Thread維護了ThreadLocalMap,而ThreadLocalMap裡維護了Entry,而Entry裡存的是以ThreadLocal為key,傳入的值為value的鍵值對。
// java.lang.Thread類裡持有ThreadLocalMap的引用 public class Thread implements Runnable { ThreadLocal.ThreadLocalMap threadLocals = null; } // java.lang.ThreadLocal有內部靜態類ThreadLocalMap public class ThreadLocal<T> { static class ThreadLocalMap { private Entry[] table; // ThreadLocalMap內部有Entry類,Entry的key是ThreadLocal本身,value是泛型值 static class Entry extends WeakReference<ThreadLocal<?>> { Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } } }
主要方法:
-
initialValue:初始化。在get方法裡懶載入的。
- 通常,每個執行緒最多呼叫一次此方法。但是如果已經呼叫了remove(),然後再次呼叫get()的話,則可以再次觸發initialValue。
- 如果要重寫的話一般建議採取匿名內部類的方式重寫此方法,否則預設返回的是null。
public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = new ThreadLocal() { @Override protected SimpleDateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); } }; // Java8的高逼格寫法 public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"));
-
get:得到這個執行緒對應的value。如果呼叫get之前沒set過,則get內部會執行initialValue方法進行初始化。
/** * 獲取當前執行緒下的entry裡的value值。 * 先獲取當前執行緒下的ThreadLocalMap, * 然後以當前ThreadLocal為key取出map中的value */ public T get() { // 獲取當前執行緒 Thread t = Thread.currentThread(); // 獲取當前執行緒對應的ThreadLocalMap物件。 ThreadLocalMap map = getMap(t); // 若獲取到了。則獲取此ThreadLocalMap下的entry物件,若entry也獲取到了,那麼直接獲取entry對應的value返回即可。 if (map != null) { // 獲取此ThreadLocalMap下的entry物件 ThreadLocalMap.Entry e = map.getEntry(this); // 若entry也獲取到了 if (e != null) { @SuppressWarnings("unchecked") // 直接獲取entry對應的value返回。 T result = (T)e.value; return result; } } // 若沒獲取到ThreadLocalMap或沒獲取到Entry,則設定初始值。 懶載入方式 return setInitialValue(); } 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 // 如果e=null,說明出現碰撞問題,通過開放定址方法來繼續尋找 return getEntryAfterMiss(key, i, e); } // 通過布長+1或-1來尋找下一個相鄰的位置 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); }
-
set:為這個執行緒設定一個新值。
/** * 設定當前執行緒的執行緒區域性變數的值 * 實際上ThreadLocal的值是放入了當前執行緒的一個ThreadLocalMap例項中,所以只能在本執行緒中訪問。 */ public void set(T value) { // 獲取當前執行緒 Thread t = Thread.currentThread(); // 獲取當前執行緒對應的ThreadLocalMap例項,注意這裡是將t傳進去了,t是當前執行緒,就是說ThreadLocalMap是線上程裡持有的引用。 ThreadLocalMap map = getMap(t); // 若當前執行緒有對應的ThreadLocalMap例項,則將當前ThreadLocal物件作為key,value做為值存到ThreadLocalMap的entry裡。 if (map != null) map.set(this, value); else // 若當前執行緒沒有對應的ThreadLocalMap例項,則建立ThreadLocalMap,並將此執行緒與之繫結 createMap(t, value); }
-
remove:ThreadLocalMap鍵為弱引用,刪除這個執行緒對應的值,防止記憶體洩露的最佳手段。
碰撞解決與神奇的 0x61c88647(十進位制:1640531527)
兩種碰撞型別
- 只有一個ThreadLocal例項的時候(上面推薦的做法),當向thread-local變數中設定多個值的時產生的碰撞,碰撞解決是通過開放定址法, 且是線性探測(linear-probe)
- 多個ThreadLocal例項的時候,最極端的是每個執行緒都new一個ThreadLocal例項,此時利用特殊的雜湊碼0x61c88647大大降低碰撞的機率, 同時利用開放定址法處理碰撞
顯然ThreadLocalMap採用線性探測的方式解決Hash衝突的效率很低,如果有大量不同的ThreadLocal物件放入map中時傳送衝突,或者發生二次衝突,則效率很低。
所以這裡引出的良好建議是:每個執行緒只存一個變數,這樣的話所有的執行緒存放到map中的Key都是相同的ThreadLocal,如果一個執行緒要儲存多個變數,就需要建立多個ThreadLocal,多個ThreadLocal放入Map中時會極大的增加Hash衝突的可能。
key.threadLocalHashCode ==> AtomicInteger.getAndAdd(HASH_INCREMENT) 其中 HASH_INCREMENT = 0x61c88647,這個值是 為了讓雜湊碼能均勻的分佈在2的N次方的數組裡
This number represents the golden ratio (sqrt(5)-1) times two to the power of 31 ((sqrt(5)-1) * (2^31)). The result is then a golden number, either 2654435769 or -1640531527.
魔數0x61c88647的與斐波那契雜湊有關,0x61c88647對應的十進位制為1640531527。斐波那契雜湊的乘數可以用(long) ((1L << 31) * (Math.sqrt(5) - 1))可以得到2654435769,如果把這個值給轉為帶符號的int,則會得到-1640531527。換句話說(1L << 32) - (long) ((1L << 31) * (Math.sqrt(5) - 1))得到的結果就是1640531527也就是0x61c88647。通過理論與實踐,當我們用0x61c88647作為魔數累加為每個ThreadLocal分配各自的ID也就是threadLocalHashCode再與2的冪取模,得到的結果分佈很均勻。