一互艾、什么是 ThreadLocal
ThreadLocal 提供了線程的局部變量蚯嫌,每個線程都可以通過 set() 和 get() 來對這個局部變量進行操作,但不會和其他線程的局部變量沖突蜂嗽,實現(xiàn)了線程間的據(jù)隔離筑凫。
簡單講:一個獲取用戶的請求線程 A嫌变,如果向 ThreadLocal 填充變量 AValue(只能被線程 A 操作)吨艇,該變量對其他獲取用戶的請求線程 B、C...是隔離的.
最簡單的使用方式:
類似一次 HTTP 請求線程中腾啥,利用 ThreadLocal 存儲 Cookie 對象东涡,進行狀態(tài)管理。set Cookie:
private ThreadLocal httpThreadLocal = new ThreadLocal();
httpThreadLocal.set(“Cookie: sid=13420771402233”)
上面存儲格式是 String 倘待,實際場景存儲的是具體的對象疮跑。在這次 HTTP 請求過程中,任何時候都可以獲取 Cookie 凸舵。獲取方式很簡單 get Cookie:
String cookieValue = (String) httpThreadLocal.get();
Thread 與 ThreadLocal 對象引用關系圖
二祖娘、你熟悉的場景
2.1 數(shù)據(jù)庫連接池
比如一次請求線程進來,業(yè)務 Dao 需要更新 user 表和 user-detail 表啊奄。如果是 new 出兩個數(shù)據(jù)庫 Connection 渐苏,分別不同的 Connection 操作 user 表和 user-detail 表,就無法保證事務菇夸。那么數(shù)據(jù)庫連接池是如何保證的琼富?
答案是:利用 ThreadLocal 存儲唯一 Connection 對象。每次請求線程庄新,pool.getConnection 獲取連接的時候都會這樣操作:
- 會從 ThreadLocal 獲取 Connection 對象鞠眉。如果有薯鼠,則保證了后面多個數(shù)據(jù)庫操作共用同一個 Connection ,從而保證了事務械蹋。
- 如果沒有出皇,往 ThreadLocal 新增Connection 對象,并返回到線程
錯誤的做法
public class XXXService {
private Connection conn;
}
因為 conn 是線程不安全的哗戈。這樣會導致多個請求公用一個連接郊艘。請求量很大的情況下,延遲各種谱醇。你懂暇仲。
因此,使用 ThreadLocal 保證每個請求線程的 Connection 是唯一的副渴。即每個線程有自己的連接。
繼續(xù)講到 Spring 框架全度,在事務開始時煮剧,會給當前線程一個Jdbc Connection,在整個事務過程,都是使用該線程綁定的connection來執(zhí)行數(shù)據(jù)庫操作将鸵,實現(xiàn)了事務的隔離性勉盅。Spring框架里面就是用的ThreadLocal來實現(xiàn)這種隔離
2.2 HTTP Cookie
比如你訪問百度、我訪問百度顶掉,會有不同 Cookie 草娜。而且你不能訪問我的 Cookie,我也不能痒筒。顧名思義宰闰,使用 ThreadLocal 保證每個 HTTP 請求線程的 Cookie 是唯一的。
Cookie 這樣才能做 Session 等狀態(tài)管理簿透。
三移袍、實戰(zhàn)場景
總結(jié)一下就是:ThreadLocal 可以讓同一個線程中上下文之間數(shù)據(jù)共享
在上面章節(jié) 二、你熟悉的場景 其實介紹了很多現(xiàn)有場景老充。那么我這邊具體的實戰(zhàn)場景是什么葡盗?
簡單的例子:
適用滿足這兩個條件的場景:1.每個線程獨有的一些信息,2.這些信息又會在多個方法或類中用到啡浊。
- 一個請求線程觅够,里面有兩個異步小線程,各有一個方法巷嚣。分別處理 A 或 B 業(yè)務
- 一種方法是傳遞不可變的入?yún)?/li>
- 另一種就是 ThreadLocal喘先,放在 ThreadLocal 的入?yún)ⅲ瑫桓鱾€方法共享涂籽。而且多個請求線程互不影響
復雜的例子:
一次發(fā)貨操作:會根據(jù)入?yún)⑵凰睿M行組件化、流程編排話。那么入?yún)桓鱾€地方用到树枫,而且有些流程組件是異步的(類似 new thread 操作的)直焙。這時候可以定一個 XXContext 上下文:
public class XXContext {
private static ThreadLocal<Map<Class<?>, Object>> context = new InheritableThreadLocal<>();
/**
* 把參數(shù)設置到上下文的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);
}
}
/**
* 從上下文中,根據(jù)類名取出參數(shù)
*/
@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的數(shù)據(jù)
*/
public static void clean() {
context.remove();
}
}
代碼解析:
- 都是 static 操作砂轻,類似 DateUtil 玩法
- 記得每次請求線程后清理奔誓。可以 AOP 去清理搔涝,加個注解就行厨喂。因為同一個請求線程可能被業(yè)務方公用。
(完)