ThreadLocal概念以及使用場景
點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號”
優(yōu)質(zhì)文章,第一時(shí)間送達(dá)
根據(jù)自身的知識深度,這里只限于自己使用和學(xué)習(xí)的知識點(diǎn)整理,原理的解釋還需要再沉淀。
該文章從項(xiàng)目開發(fā)中舉例,希望能幫助到各位,不了解ThreadLocal的朋友,可能會問,這是個(gè)是什么,這有什么用,這能用在哪些地方,接下來我一一解釋,如果有地方解釋不好,希望多多包含。
概念:
這是個(gè)是什么:
ThreadLocal是一個(gè)類,是一個(gè)本地線程,提供了一種線程安全的方式,用來避免共享數(shù)據(jù)(線程變量隔離)。
翻看源碼可以看到注釋(已翻譯):
此類提供線程局部變量。這些變量不同于它們的普通對應(yīng)變量,因?yàn)槊總€(gè)訪問一個(gè)(通過其get或set方法)的線程都有自己的、獨(dú)立初始化的變量副本。ThreadLocal實(shí)例通常是希望將狀態(tài)與線程相關(guān)聯(lián)的類中的私有靜態(tài)字段(例如,用戶 ID 或事務(wù) ID)
這有什么用:
按照《Java核心卷一》來說,有時(shí)候可能要避免共享變量,使用ThreadLocal輔助類為各個(gè)線程提供各自的實(shí)例;
就是說,每個(gè)線程都有一個(gè)伴生的空間(ThreadLocal),存儲私有的數(shù)據(jù)。

只要線程存在,就能拿到對應(yīng)的ThreadLocal中存儲的值。
創(chuàng)建以及提供的方法
創(chuàng)建一個(gè)線程局部變量,其初始值通過調(diào)用給定的提供者(Supplier)生成;
public?static??ThreadLocal?withInitial(Supplier?extends?S>?supplier)?{
????????return?new?SuppliedThreadLocal<>(supplier);
????}使用的方法也比較少:

這里就列出用的比較多的方法:
返回此線程局部變量的當(dāng)前線程副本中的值。如果該變量對于當(dāng)前線程沒有值,則首先將其初始化為調(diào)用initialValue方法返回的值:
T?get()將此線程局部變量的當(dāng)前線程副本設(shè)置為指定值;
value -- 要存儲在此線程本地的當(dāng)前線程副本中的值
void?set(T?value)刪除此線程局部變量的當(dāng)前線程值(刪除這里有些講究,暫且不表,留在后面)
void?remove()項(xiàng)目實(shí)例:
ThreadLocal使用的場景主要是:(引用)
每個(gè)線程需要自己獨(dú)立的實(shí)例且該實(shí)例需要在多個(gè)方法中使用
以下是個(gè)人使用的場景:
自己為什么會使用它,我是想在項(xiàng)目中直接獲取當(dāng)前用戶的信息,這個(gè)功能就使用了ThreadLocal;
首先創(chuàng)建一個(gè)ThreadLocal類:
import?com.xbhog.springbootvueblog.pojo.SysUser;
public?class?UserThreadLocal?{
????private??UserThreadLocal(){}
????private?static?final?ThreadLocal?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();
????}
}
其中包含了數(shù)據(jù)的添加刪除和獲取,因?yàn)槲覀冃枰氖怯脩粜畔ⅲ敲淳桶裊ser類傳入泛型中;
操作的對象有了,接下來就是使用:
在該項(xiàng)目中個(gè)人使用的地方在登錄攔截器中,當(dāng)對登錄的信息檢查成功后,那么將當(dāng)前的用戶對象加入到ThreadLocal中,
//登錄驗(yàn)證成功,放行
System.out.println("走到UserThreadLocal--------");
UserThreadLocal.put(sysUser);在controller中使用的時(shí)候,直接調(diào)用ThreadLocal中的get方法,就可以獲得當(dāng)前用戶的信息:
//獲取當(dāng)前在線用戶信息
SysUser?sysUser?=?UserThreadLocal.get();資源調(diào)用完成后需要在攔截器中刪除ThreadLocal資源:
@Override
public?void?afterCompletion(HttpServletRequest?request,?HttpServletResponse?response,?Object?handler,?Exception?ex)?throws?Exception?{
????//使用完的用戶信息需要?jiǎng)h除,防止內(nèi)存泄露
????UserThreadLocal.remove();
}
afterCompletion作用:
實(shí)現(xiàn)最終的完成后進(jìn)行處理,該方法在執(zhí)行完控制器之后執(zhí)行,由于是在Controller方法執(zhí)行完畢后執(zhí)行該方法,所以該方法適合進(jìn)行一些資源清理,記錄日志信息等處理操作
什么意思呢:
程序首先執(zhí)行攔截器類中的preHandle()方法,如果該方法返回值是true,則程序會繼續(xù)向下執(zhí)行處理器中的方法,否則不再向下執(zhí)行;在業(yè)務(wù)控制器類Controller(這里就可以用ThreadLocal 獲取用戶信息)處理完請求后,會執(zhí)行postHandle()方法,
而后會通過DispatcherServlet向客戶端返回響應(yīng);在DispatcherServlet處理完請求后,才會執(zhí)行afterCompletion()方法(這里刪除這次請求的ThreadLocal 中的user信息);
ThreadLocal的內(nèi)存泄露問題:
如果我們在使用完該線程后不進(jìn)行ThreadLocal 中的變量進(jìn)行刪除,那么就會造成內(nèi)存泄漏的問題,那么該問題是怎么出現(xiàn)的?
首先先看一下ThreadLocal 內(nèi)部結(jié)構(gòu):

首先對于對象在棧中保存的是對象的引用,對象的存儲在堆中,要先明確概念。棧中的格式也符合我們上述我們畫的圖1,其中Heap中的map是ThreadLocal map,里面包含key和value,其中value就是我們需要保存的變量數(shù)據(jù),key則是ThreadLocal實(shí)例;
即:每一個(gè)Thread維護(hù)一個(gè)ThreadLocalMap, key為使用弱引用的ThreadLocal實(shí)例,value為線程變量的副本。
還需要注意的是上述圖片的連接有實(shí)線和虛線,實(shí)線代表強(qiáng)引用,虛線表示弱引用;
掃盲強(qiáng)引用、軟引用、弱引用、虛引用:??
強(qiáng)引用,我們使用的大部分引用都是強(qiáng)引用,特點(diǎn)就是當(dāng)內(nèi)存不足時(shí),垃圾回收器寧愿自己拋出OOM,也不會回收強(qiáng)引用來解決內(nèi)存不足的問題
軟引用,就是生殺大權(quán)都在垃圾回收器中,我內(nèi)存夠的話,你可以活著,如果不夠的話,我就回收你,給我騰地方;
弱引用,只要垃圾回收器掃到,不管空間夠不夠,都給我回收了;
虛引用,形同虛設(shè)的東西,在任何情況下都可能被回收
這里只給出了概念,如果感興趣可以自行了解作用環(huán)境。
那么知道強(qiáng)引用、弱引用后我們再來看圖,因?yàn)門hreadLocal與線程屬于同一個(gè)生命周期,當(dāng)在某一個(gè)階段進(jìn)行了一次GC,那么當(dāng)前線程還在,但是ThreadLocal實(shí)例被回收了,也就是key沒了,
我們都知道,map中的value需要key找到,key沒了,那么value就會永遠(yuǎn)的留在內(nèi)存中,直到內(nèi)存滿了,導(dǎo)致OOM,所以我們就需要使用完以后進(jìn)行手動(dòng)刪除,這樣能保證不會出現(xiàn)因?yàn)镚C的原因造成的OOM問題;

? 作者?|??xbhog
來源 |??cnblogs.com/xbhog/p/15411167.html

