起因
最近進入專案組開發,偶爾翻閱別人程式碼時,看到如下注釋。
注:該ThreadLocalUtil 就是內部使用 ThreadLocal 內部靜態變數。
剛看到時完全不解,不知道其為什麼這麼說。當同事跟我解釋原因,豁然開朗。細想,我們在工作使用中,如果不注意很容易就忽略這個問題。
在解釋這個問題先,我們先來檢視 ThreadLocal。
ThreadLocal
ThreadLocal 提供執行緒區域性(thread-local)變數,為每個執行緒建立單獨的變數副本,這樣在多執行緒環境的下,由於每個執行緒都有單獨的變數,不會因為變數共享導致的併發問題。當然相關問題我可以們使用同步機制解決該問題。
同步機制跟 ThreadLocal 區別
使用同步機制,多執行緒環境共享同一變數,這需要我們在使用時顯示加鎖,保證變數在同一時間只有一個執行緒能使用,相當於採用時間策略換取執行緒安全。
使用 ThreadLocal,每個執行緒擁有自己獨立變數副本,相當於採用空間策略換取執行緒安全。
我們從程式碼檢視下兩種方式的區別。現在假如我們需要當前一個時間工具類,如下:
private final static SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static String getDateStr(Date date) {
return format.format(date);
}
複製程式碼
檢視上面 Demo,我們很容易可以看出上面方法由於是 SimpleDateFormat 不是執行緒安全,從而導致 getDateStr 方法非執行緒安全。
現在我們使用同步對其改造。
private final static SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static synchronized String getDateStrSync(Date date) {
return format.format(date);
}
複製程式碼
這樣使用顯示加鎖,避免多執行緒環境下執行緒安全。但是這種方式可能在高併發情況影響效率。
下面使用 ThreadLocal 對其改造。
private final static ThreadLocal<SimpleDateFormat> formatLocal = new ThreadLocal<SimpleDateFormat>() {
protected synchronized SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};
public static String getDateByLocal(Date date){
return formatLocal.get().format(date);
}
複製程式碼
使用 ThreadLocal,將共享變數變成獨享變數,保證執行緒安全。但是該種方案增加建立物件的開銷。
綜上,如何選擇上述兩種方式,需要結合當前業務方式選擇。
講完 ThreadLocal,我們來看下的 dubbo 的執行緒模型。
dubbo 執行緒模型
dubbo 預設採用單一長連線加執行緒池方式處理呼叫。
預設採取 Dispatcher=all 的分發策略,所有訊息都派發到執行緒池,包括請求,響應,連線事件,斷開事件,心跳等。執行緒池在預設配置為固定大小執行緒池,啟動時建立執行緒,不關閉,一直持有。預設為100個執行緒。
分析在 Dubbo 中使用 ThreadLocal
在 Dubbo 中使用 ThreadLocal ,如果採用預設的設定,每次 Dubbo 呼叫結束,Dubbo 處理響應執行緒並不會被銷燬, 而是歸還到執行緒池中。而從 ThreadLocal 原始碼可以看出,每次我們設定的值其實會存在位於 Thread 中 ThreadLocalMap 變數中。
這就導致,下次如果 Dubbo 處理響應恰好繼續使用到這個執行緒,該執行緒就能呼叫到上次響應中設定在 ThreadLocal 設定的值。這就引起記憶體洩露,可能還會導致業務上異常。其實並不止在 Dubbo 中,該案例還會發生在 web專案中,只要相關使用執行緒池的,都有可能發生。
Threadlocal 總結
使用 Threadlocal,我們需要注意幾點:
- 使用 Threadlocal 需要跟使用相關流一樣,需要最後顯示呼叫其 remove 方法,清除其設定的值,防止引起記憶體洩露。
- 不能什麼傳參都使用 Threadlocal 在方法中上下傳遞,這樣就會引起隱形耦合,而且相關程式碼並不好維護。
- 高併發中,我們可以使用 Threadlocal 代替同步鎖,提高相關效能。
參考連結
- 【死磕Java併發】—–深入分析ThreadLocal
- 聊一聊Spring中的執行緒安全性
- 優雅的使用 ThreadLocal 傳遞引數
- www.baeldung.com/java-thread…
- www.cnblogs.com/youzhibing/…
- dubbo 執行緒模型
- How to shoot yourself in foot with ThreadLocals
如果覺得好的話,請幫作者點個讚唄~ 謝謝
喜歡本文的讀者們,歡迎長按關注訂閱號程式通事~讓我與你分享程式那些事。