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信息);
使用場景2
項目中針對日期進行的格式化SimpleDateFormat
,眾所周知,其使用是線程不安全的,所以在項目中通過ThreadLocal來進行變量副本的創建;
為什么選擇 ThreadLocal 呢? 其實也可以使用局部變量實現,但是有點low;通過ThreaLocal實現的線程變量隔離,是一個無鎖的操作,對程序的性能有一定的提升。
為什么說是一個無鎖的性能,因為SimpleDateFormat
是線程不安全的,平常的解決方式加鎖,像synchronized實現,都需要進行同步,既然同步了,那么必然會有性能減少的問題。
ThreadLocal
和synchronized
的主要區別就是,threadLocal
是空間換時間,synchronized
是時間換空間.
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攔截器的應用,建議收藏再看!
結束:
如果你看到這里或者正好對你有所幫助,希望能點個👍或者⭐感謝;
有錯誤的地方,歡迎在評論指出,作者看到會進行修改。