在 dubbo 中使用 Threadlocal 的相關問題

程式通事發表於2019-02-28

起因

最近進入專案組開發,偶爾翻閱別人程式碼時,看到如下注釋。

俏皮註釋

注:該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 預設採用單一長連線加執行緒池方式處理呼叫。

dubbo 呼叫圖

預設採取 Dispatcher=all 的分發策略,所有訊息都派發到執行緒池,包括請求,響應,連線事件,斷開事件,心跳等。執行緒池在預設配置為固定大小執行緒池,啟動時建立執行緒,不關閉,一直持有。預設為100個執行緒。

分析在 Dubbo 中使用 ThreadLocal

在 Dubbo 中使用 ThreadLocal ,如果採用預設的設定,每次 Dubbo 呼叫結束,Dubbo 處理響應執行緒並不會被銷燬, 而是歸還到執行緒池中。而從 ThreadLocal 原始碼可以看出,每次我們設定的值其實會存在位於 Thread 中 ThreadLocalMap 變數中。

Thread與ThreadLocal物件之間的引用關係圖

這就導致,下次如果 Dubbo 處理響應恰好繼續使用到這個執行緒,該執行緒就能呼叫到上次響應中設定在 ThreadLocal 設定的值。這就引起記憶體洩露,可能還會導致業務上異常。其實並不止在 Dubbo 中,該案例還會發生在 web專案中,只要相關使用執行緒池的,都有可能發生。

Threadlocal 總結

使用 Threadlocal,我們需要注意幾點:

  1. 使用 Threadlocal 需要跟使用相關流一樣,需要最後顯示呼叫其 remove 方法,清除其設定的值,防止引起記憶體洩露。
  2. 不能什麼傳參都使用 Threadlocal 在方法中上下傳遞,這樣就會引起隱形耦合,而且相關程式碼並不好維護。
  3. 高併發中,我們可以使用 Threadlocal 代替同步鎖,提高相關效能。

參考連結

  1. 【死磕Java併發】—–深入分析ThreadLocal
  2. 聊一聊Spring中的執行緒安全性
  3. 優雅的使用 ThreadLocal 傳遞引數
  4. www.baeldung.com/java-thread…
  5. www.cnblogs.com/youzhibing/…
  6. dubbo 執行緒模型
  7. How to shoot yourself in foot with ThreadLocals

如果覺得好的話,請幫作者點個讚唄~ 謝謝

喜歡本文的讀者們,歡迎長按關注訂閱號程式通事~讓我與你分享程式那些事。

在 dubbo 中使用 Threadlocal 的相關問題

相關文章