ThreadLocal概念以及使用場景
根據自身的知識深度,這裡只限於自己使用和學習的知識點整理,原理的解釋還需要再沉澱。
該文章從專案開發中舉例,希望能幫助到各位,不瞭解ThreadLocal的朋友,可能會問,這是個是什麼,這有什麼用,這能用在哪些地方,接下來我一一解釋,如果有地方解釋不好,希望多多包含。
概念:
這是個是什麼:
ThreadLocal是一個類,是一個本地執行緒,提供了一種執行緒安全的方式,用來避免共享資料(執行緒變數隔離)。
翻看原始碼可以看到註釋(已翻譯):
此類提供執行緒區域性變數。 這些變數不同於它們的普通對應變數,因為每個訪問一個(通過其get或set方法)的執行緒都有自己的、獨立初始化的變數副本。 ThreadLocal例項通常是希望將狀態與執行緒相關聯的類中的私有靜態欄位(例如,使用者 ID 或事務 ID)
這有什麼用:
按照《Java核心卷一》來說,有時候可能要避免共享變數,使用ThreadLocal輔助類為各個執行緒提供各自的例項;
就是說,每個執行緒都有一個伴生的空間(ThreadLocal),儲存私有的資料。
只要執行緒存在,就能拿到對應的ThreadLocal中儲存的值。
建立以及提供的方法
建立一個執行緒區域性變數,其初始值通過呼叫給定的提供者(Supplier)生成;
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
return new SuppliedThreadLocal<>(supplier);
}
使用的方法也比較少:
這裡就列出用的比較多的方法:
返回此執行緒區域性變數的當前執行緒副本中的值。 如果該變數對於當前執行緒沒有值,則首先將其初始化為呼叫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為執行緒變數的副本。
還需要注意的是上述圖片的連線有實線和虛線,實線代表強引用,虛線表示弱引用;
掃盲強引用、軟引用、弱引用、虛引用:?
- 強引用,我們使用的大部分引用都是強引用,特點就是當記憶體不足時,垃圾回收器寧願自己丟擲OOM,也不會回收強引用來解決記憶體不足的問題
- 軟引用,就是生殺大權都在垃圾回收器中,我記憶體夠的話,你可以活著,如果不夠的話,我就回收你,給我騰地方;
- 弱引用,只要垃圾回收器掃到,不管空間夠不夠,都給我回收了;
- 虛引用,形同虛設的東西,在任何情況下都可能被回收
這裡只給出了概念,如果感興趣可以自行了解作用環境。
那麼知道強引用、弱引用後我們再來看圖,因為ThreadLocal與執行緒屬於同一個生命週期,當在某一個階段進行了一次GC,那麼當前執行緒還在,但是ThreadLocal例項被回收了,也就是key沒了,
我們都知道,map中的value需要key找到,key沒了,那麼value就會永遠的留在記憶體中,直到記憶體滿了,導致OOM,所以我們就需要使用完以後進行手動刪除,這樣能保證不會出現因為GC的原因造成的OOM問題;
參考文獻:
寫的太細了!Spring MVC攔截器的應用,建議收藏再看!
結束:
如果你看到這裡或者正好對你有所幫助,希望能點個?或者⭐感謝;
有錯誤的地方,歡迎在評論指出,作者看到會進行修改。