ThreadLocal概念以及使用場景

xbhog發表於2021-10-15

ThreadLocal概念以及使用場景

根據自身的知識深度,這裡只限於自己使用和學習的知識點整理,原理的解釋還需要再沉澱。

該文章從專案開發中舉例,希望能幫助到各位,不瞭解ThreadLocal的朋友,可能會問,這是個是什麼,這有什麼用,這能用在哪些地方,接下來我一一解釋,如果有地方解釋不好,希望多多包含。

概念:

這是個是什麼:

ThreadLocal是一個類,是一個本地執行緒,提供了一種執行緒安全的方式,用來避免共享資料(執行緒變數隔離)。

翻看原始碼可以看到註釋(已翻譯):

此類提供執行緒區域性變數。 這些變數不同於它們的普通對應變數,因為每個訪問一個(通過其get或set方法)的執行緒都有自己的、獨立初始化的變數副本。 ThreadLocal例項通常是希望將狀態與執行緒相關聯的類中的私有靜態欄位(例如,使用者 ID 或事務 ID)

這有什麼用:

按照《Java核心卷一》來說,有時候可能要避免共享變數,使用ThreadLocal輔助類為各個執行緒提供各自的例項;

就是說,每個執行緒都有一個伴生的空間(ThreadLocal),儲存私有的資料。

image-20211014212949769

只要執行緒存在,就能拿到對應的ThreadLocal中儲存的值。

建立以及提供的方法

建立一個執行緒區域性變數,其初始值通過呼叫給定的提供者(Supplier)生成;

public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
        return new SuppliedThreadLocal<>(supplier);
    }

使用的方法也比較少:

image-20211014213830530

這裡就列出用的比較多的方法:

返回此執行緒區域性變數的當前執行緒副本中的值。 如果該變數對於當前執行緒沒有值,則首先將其初始化為呼叫initialValue方法返回的值:

T get()

將此執行緒區域性變數的當前執行緒副本設定為指定值;

value -- 要儲存在此執行緒本地的當前執行緒副本中的值

void set(T value)

刪除此執行緒區域性變數的當前執行緒值(刪除這裡有些講究,暫且不表,留在後面)

void remove()

專案例項:

ThreadLocal使用的場景主要是:(引用)

每個執行緒需要自己獨立的例項且該例項需要在多個方法中使用

以下是個人使用的場景:

自己為什麼會使用它,我是想在專案中直接獲取當前使用者的資訊,這個功能就使用了ThreadLocal;

首先建立一個ThreadLocal類:

import com.xbhog.springbootvueblog.pojo.SysUser;

public class UserThreadLocal {
    private  UserThreadLocal(){}

    private static final ThreadLocal<SysUser> LOCAL = new ThreadLocal<>();

    public static void put(SysUser sysUser){
        LOCAL.set(sysUser);
    }
    public static SysUser get(){
        return LOCAL.get();
    }

    public static void remove(){
        LOCAL.remove();
    }
}

其中包含了資料的新增刪除和獲取,因為我們需要的是使用者資訊,那麼就把User類傳入泛型中;

操作的物件有了,接下來就是使用:

在該專案中個人使用的地方在登入攔截器中,當對登入的資訊檢查成功後,那麼將當前的使用者物件加入到ThreadLocal中,

//登入驗證成功,放行
System.out.println("走到UserThreadLocal--------");
UserThreadLocal.put(sysUser);

在controller中使用的時候,直接呼叫ThreadLocal中的get方法,就可以獲得當前使用者的資訊:

//獲取當前線上使用者資訊
SysUser sysUser = UserThreadLocal.get();

資源呼叫完成後需要在攔截器中刪除ThreadLocal資源:

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    //使用完的使用者資訊需要刪除,防止記憶體洩露
    UserThreadLocal.remove();
}

afterCompletion作用:

實現最終的完成後進行處理,該方法在執行完控制器之後執行,由於是在Controller方法執行完畢後執行該方法,所以該方法適合進行一些資源清理,記錄日誌資訊等處理操作

什麼意思呢:

程式首先執行攔截器類中的preHandle()方法,如果該方法返回值是true,則程式會繼續向下執行處理器中的方法,否則不再向下執行;在業務控制器類Controller(這裡就可以用ThreadLocal 獲取使用者資訊)處理完請求後,會執行postHandle()方法,

而後會通過DispatcherServlet向客戶端返回響應;在DispatcherServlet處理完請求後,才會執行afterCompletion()方法(這裡刪除這次請求的ThreadLocal 中的user資訊);

ThreadLocal的記憶體洩露問題:

如果我們在使用完該執行緒後不進行ThreadLocal 中的變數進行刪除,那麼就會造成記憶體洩漏的問題,那麼該問題是怎麼出現的?

首先先看一下ThreadLocal 內部結構:

首先對於物件在棧中儲存的是物件的引用,物件的儲存在堆中,要先明確概念。棧中的格式也符合我們上述我們畫的圖1,其中Heap中的map是ThreadLocal map,裡面包含key和value,其中value就是我們需要儲存的變數資料,key則是ThreadLocal例項;

即:每一個Thread維護一個ThreadLocalMap, key為使用弱引用的ThreadLocal例項,value為執行緒變數的副本。

還需要注意的是上述圖片的連線有實線和虛線,實線代表強引用,虛線表示弱引用;

掃盲強引用、軟引用、弱引用、虛引用:?

  1. 強引用,我們使用的大部分引用都是強引用,特點就是當記憶體不足時,垃圾回收器寧願自己丟擲OOM,也不會回收強引用來解決記憶體不足的問題
  2. 軟引用,就是生殺大權都在垃圾回收器中,我記憶體夠的話,你可以活著,如果不夠的話,我就回收你,給我騰地方;
  3. 弱引用,只要垃圾回收器掃到,不管空間夠不夠,都給我回收了;
  4. 虛引用,形同虛設的東西,在任何情況下都可能被回收

這裡只給出了概念,如果感興趣可以自行了解作用環境。

那麼知道強引用、弱引用後我們再來看圖,因為ThreadLocal與執行緒屬於同一個生命週期,當在某一個階段進行了一次GC,那麼當前執行緒還在,但是ThreadLocal例項被回收了,也就是key沒了,

我們都知道,map中的value需要key找到,key沒了,那麼value就會永遠的留在記憶體中,直到記憶體滿了,導致OOM,所以我們就需要使用完以後進行手動刪除,這樣能保證不會出現因為GC的原因造成的OOM問題;

image-20211015145718238

參考文獻:

ThreadLocal解決了什麼問題

SpringMVC---攔截器

寫的太細了!Spring MVC攔截器的應用,建議收藏再看!

結束:

如果你看到這裡或者正好對你有所幫助,希望能點個?或者⭐感謝;

有錯誤的地方,歡迎在評論指出,作者看到會進行修改。

相關文章