前言
在家辦公 ?,不敢出門 ?,不敢理髮 ?
沒錯,和大家一樣,疫情結束後,我們就是鎮樓圖這個樣子。
並且,你是不是也好幾天沒洗頭了呢?
言歸正傳 ~
本篇文章來源於記憶中的一道題,實現一個 ThreadLocal。
筆者第一次碰到這個題的時候,當時可是非常天真,分分鐘寫了一下,結果發現是個低配版,請你也繼續往下看看,有沒有和筆者一樣天真過。
正文
低配 ThreadLocal 實現
在生活中的話,想知道一個東西優點在哪裡,該怎麼辦?
幹說,估計大多數人都聽不懂。 但是,如果有同類產品作比較,那麼優缺就顯而易見了。那麼同樣的,下文中用筆者的低配版MyThreadLocal來看看ThreadLocal有什麼優點。
其實當時看到這題的時候,還沒仔細看過ThreadLocal的原始碼,但是腦中還是有幾個關鍵字的,Map,弱引用,於是動手就寫了一個版本, 大體實現是這樣的:
public class ThreadLocal<T> {
private Map<Thread, T> threadValMap = Collections.synchronizedMap(new WeakHashMap<>());
public void set(T value) {
threadValMap.put(Thread.currentThread(), value);
}
public T get() {
return threadValMap.get(Thread.currentThread());
}
// remove....
}
複製程式碼
你別說,測試了一下,基本功能沒毛病,Thread用完之後消除引用,通知 GC,過一會鍵值也成功被回收。
但是翻開 ThreadLocal 本身的原始碼,設計可是大相徑庭。
這個低配版和正統版有什麼區別?
對比兩個ThreadLocal
上文提到的自己實現的ThreadLocal,下文我就一直稱呼為MyThreadLocal吧。
MyThreadLocal的實現很簡單,藉助synchronizedMap
方法實現了一個同步的弱引用HashMap。
那麼ThreadLocal內部是怎樣實現的,我也畫了一張簡圖:
不同點之一 :效率
由於Thread類中設計時就帶了一行ThreadLocal.ThreadLocalMap threadLocals = null;
作為屬性。
所以,ThreadLocal在賦值的時候,只需要檢查Thread類中的ThreadLocalMap是不是空的,空的就建立一個,不空就接著用。
上面兩段文字描述的就是MyThreadLocal和ThreadLocal的第一處不同點:
低配版用synchronizedMap做為同步容器,synchronizedMap是同步容器,並不是併發優化容器,原始碼自然是大量的synchronized
.
而ThreadLocal並沒有同步操作,而是操作都是隻針對自己當前執行緒進行操作,是天然的執行緒封閉。 用DB設計作為比喻的話,可以大致上類比為這個場景:
“單表加個欄位就完事了,你非要再建個關聯表,還得注意事務問題”。
當然了,DB設計是有不同之處的, 這樣效能就高下立判了,同樣我們在單程式編碼時也要注意,巧用不可變物件和執行緒封閉,效能要優於同步和鎖。
不同點之二:Map的Key型別
從上面的圖片可以看到,MyThreadLocal的Map直接用Thread作為Key,而ThreadLocal 的Map用的是自己本身。
上文我也提到了,Thread直接作為弱引用的Key,GC也是成功回收了,那麼區別在哪?
區別就是隻有測試的時候,你才會new Thread();
實際中執行緒交由執行緒池管理,執行緒池內部採用執行緒複用,那麼Thread將一直保持引用,不能被GC掉,可能導致記憶體洩露。
ThreadLocal倒是也有類似的問題,因為大多數情況我們都會使用static引用ThreadLocal,一直保持強引用。好在ThreadLocal提供了remove方法,只需開發者注意用完後呼叫即可。
最後
通過當時的筆者寫出的低配版MyThreadLocal, 大概也知道了ThreadLocal優點在哪了,同時也感覺有個細節比較有趣:
ThreadLocalMap
內部並沒有使用HashMap做輔助,而且直接手寫了一個HashMap。
當時看到ThreadLocalMap的程式碼時,以為大佬們是想秀一下資料結構的功底,實際上,hashMap的誕生要晚於ThreadLocalMap,才是沒有用的原因之一。
想想也是這樣,Doug Lea和Josh Bloch這種級別的,還用秀基本功嗎?...