真實項目中 ThreadLocal 的妙用

1、什麼是 ThreadLocal

ThreadLocal 提供了線程的局部變量,每一個線程均可以經過 set() 和 get() 來對這個局部變量進行操做,但不會和其餘線程的局部變量衝突,實現了線程間的據隔離。數據庫

簡單講:一個獲取用戶的請求線程 A,若是向 ThreadLocal 填充變量 AValue(只能被線程 A 操做),該變量對其餘獲取用戶的請求線程 B、C...是隔離的.安全

最簡單的使用方式:

相似一次 HTTP 請求線程中,利用 ThreadLocal 存儲 Cookie 對象,進行狀態管理。set Cookie:cookie

private ThreadLocal httpThreadLocal = new ThreadLocal();

httpThreadLocal.set(「Cookie: sid=13420771402233」)
複製代碼

上面存儲格式是 String ,實際場景存儲的是具體的對象。在此次 HTTP 請求過程當中,任什麼時候候均可以獲取 Cookie 。獲取方式很簡單 get Cookie:app

String cookieValue = (String) httpThreadLocal.get();複製代碼

56330542aaf5c815e1a027671c21435d.png

Thread 與 ThreadLocal 對象引用關係圖框架

2、你熟悉的場景

2.1 數據庫鏈接池

好比一次請求線程進來,業務 Dao 須要更新 user 表和 user-detail 表。若是是 new 出兩個數據庫 Connection ,分別不一樣的 Connection 操做 user 表和 user-detail 表,就沒法保證事務。那麼數據庫鏈接池是如何保證的?異步

答案是:利用 ThreadLocal 存儲惟一 Connection 對象。每次請求線程,pool.getConnection 獲取鏈接的時候都會這樣操做:組件化

  • 會從 ThreadLocal 獲取 Connection 對象。若是有,則保證了後面多個數據庫操做共用同一個 Connection ,從而保證了事務。
  • 若是沒有,往 ThreadLocal 新增Connection 對象,並返回到線程
錯誤的作法

public class XXXService {

    private Connection conn;
}複製代碼

由於 conn 是線程不安全的。這樣會致使多個請求公用一個鏈接。請求量很大的狀況下,延遲各類。你懂。spa

所以,使用 ThreadLocal 保證每一個請求線程的 Connection 是惟一的。即每一個線程有本身的鏈接。線程

繼續講到 Spring 框架,在事務開始時,會給當前線程一個Jdbc Connection,在整個事務過程,都是使用該線程綁定的connection來執行數據庫操做,實現了事務的隔離性。Spring框架裏面就是用的ThreadLocal來實現這種隔離code

2.2 HTTP Cookie

好比你訪問百度、我訪問百度,會有不一樣 Cookie 。並且你不能訪問個人 Cookie,我也不能。顧名思義,使用 ThreadLocal 保證每一個 HTTP 請求線程的 Cookie 是惟一的。

Cookie 這樣才能作 Session 等狀態管理。

3、實戰場景

總結一下就是:ThreadLocal 可讓同一個線程中上下文之間數據共享

在上面章節 2、你熟悉的場景 其實介紹了不少現有場景。那麼我這邊具體的實戰場景是什麼?

簡單的例子:

適用知足這兩個條件的場景:1.每一個線程獨有的一些信息,2.這些信息又會在多個方法或類中用到。

  1. 一個請求線程,裏面有兩個異步小線程,各有一個方法。分別處理 A 或 B 業務
  2. 一種方法是傳遞不可變的入參
  3. 另外一種就是 ThreadLocal,放在 ThreadLocal 的入參,會被各個方法共享。並且多個請求線程互不影響
複雜的例子:

一次發貨操做:會根據入參,進行組件化、流程編排話。那麼入參會被各個地方用到,並且有些流程組件是異步的(相似 new thread 操做的)。這時候能夠定一個 XXContext 上下文:

public class XXContext {
    
    private static ThreadLocal<Map<Class<?>, Object>> context = new InheritableThreadLocal<>();
    
    /**
     * 把參數設置到上下文的Map中
     */
    public static void put(Object obj) {
        Map<Class<?>, Object> map = context.get();
        if (map == null) {
            map = new HashMap<>();
            context.set(map);
        }
        if (obj instanceof Enum) {
            map.put(obj.getClass().getSuperclass(), obj);
        } else {
            map.put(obj.getClass(), obj);
        }
    }
    
    /**
     * 從上下文中,根據類名取出參數
     */
    @SuppressWarnings("unchecked")
    public static <T> T get(Class<T> c) {
        Map<Class<?>, Object> map = context.get();
        if (map == null) {
            return null;
        }
        return (T) map.get(c);
    }
    
    /**
     * 清空ThreadLocal的數據
     */
    public static void clean() {
        context.remove();
    }
}複製代碼

代碼解析:

  • 都是 static 操做,相似 DateUtil 玩法
  • 記得每次請求線程後清理。能夠 AOP 去清理,加個註解就行。由於同一個請求線程可能被業務方公用。

(完)

file

相關文章
相關標籤/搜索