ThreadLocal概念以及使用場景


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信息);

使用場景2

項目中針對日期進行的格式化SimpleDateFormat,眾所周知,其使用是線程不安全的,所以在項目中通過ThreadLocal來進行變量副本的創建;

為什么選擇 ThreadLocal 呢? 其實也可以使用局部變量實現,但是有點low;通過ThreaLocal實現的線程變量隔離,是一個無鎖的操作,對程序的性能有一定的提升。

為什么說是一個無鎖的性能,因為SimpleDateFormat是線程不安全的,平常的解決方式加鎖,像synchronized實現,都需要進行同步,既然同步了,那么必然會有性能減少的問題。

ThreadLocalsynchronized的主要區別就是,threadLocal是空間換時間,synchronized是時間換空間.

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攔截器的應用,建議收藏再看!

結束:

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

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM