真实项目中 ThreadLocal 的妙用
一、什么是 ThreadLocal
ThreadLocal 提供了線程的局部變量,每個線程都可以通過 set() 和 get() 來對這個局部變量進(jìn)行操作,但不會和其他線程的局部變量沖突,實(shí)現(xiàn)了線程間的據(jù)隔離。
簡單講:一個獲取用戶的請求線程 A,如果向 ThreadLocal 填充變量 AValue(只能被線程 A 操作),該變量對其他獲取用戶的請求線程 B、C...是隔離的.
最簡單的使用方式:
類似一次 HTTP 請求線程中,利用 ThreadLocal 存儲 Cookie 對象,進(jìn)行狀態(tài)管理。set Cookie:
private ThreadLocal httpThreadLocal = new ThreadLocal();httpThreadLocal.set(“Cookie: sid=13420771402233”)上面存儲格式是 String ,實(shí)際場景存儲的是具體的對象。在這次 HTTP 請求過程中,任何時(shí)候都可以獲取 Cookie 。獲取方式很簡單 get Cookie:
String cookieValue = (String) httpThreadLocal.get();Thread 與 ThreadLocal 對象引用關(guān)系圖
二、你熟悉的場景
2.1 數(shù)據(jù)庫連接池
比如一次請求線程進(jìn)來,業(yè)務(wù) Dao 需要更新 user 表和 user-detail 表。如果是 new 出兩個數(shù)據(jù)庫 Connection ,分別不同的 Connection 操作 user 表和 user-detail 表,就無法保證事務(wù)。那么數(shù)據(jù)庫連接池是如何保證的?
答案是:利用 ThreadLocal 存儲唯一 Connection 對象。每次請求線程,pool.getConnection 獲取連接的時(shí)候都會這樣操作:
- 會從 ThreadLocal 獲取 Connection 對象。如果有,則保證了后面多個數(shù)據(jù)庫操作共用同一個 Connection ,從而保證了事務(wù)。
- 如果沒有,往 ThreadLocal 新增Connection 對象,并返回到線程
錯誤的做法
public class XXXService {private Connection conn; }因?yàn)?conn 是線程不安全的。這樣會導(dǎo)致多個請求公用一個連接。請求量很大的情況下,延遲各種。你懂。
因此,使用 ThreadLocal 保證每個請求線程的 Connection 是唯一的。即每個線程有自己的連接。
繼續(xù)講到 Spring 框架,在事務(wù)開始時(shí),會給當(dāng)前線程一個Jdbc Connection,在整個事務(wù)過程,都是使用該線程綁定的connection來執(zhí)行數(shù)據(jù)庫操作,實(shí)現(xiàn)了事務(wù)的隔離性。Spring框架里面就是用的ThreadLocal來實(shí)現(xiàn)這種隔離
2.2 HTTP Cookie
比如你訪問百度、我訪問百度,會有不同 Cookie 。而且你不能訪問我的 Cookie,我也不能。顧名思義,使用 ThreadLocal 保證每個 HTTP 請求線程的 Cookie 是唯一的。
Cookie 這樣才能做 Session 等狀態(tài)管理。
三、實(shí)戰(zhàn)場景
總結(jié)一下就是:ThreadLocal 可以讓同一個線程中上下文之間數(shù)據(jù)共享
在上面章節(jié) 二、你熟悉的場景 其實(shí)介紹了很多現(xiàn)有場景。那么我這邊具體的實(shí)戰(zhàn)場景是什么?
簡單的例子:
適用滿足這兩個條件的場景:1.每個線程獨(dú)有的一些信息,2.這些信息又會在多個方法或類中用到。
復(fù)雜的例子:
一次發(fā)貨操作:會根據(jù)入?yún)?#xff0c;進(jìn)行組件化、流程編排話。那么入?yún)桓鱾€地方用到,而且有些流程組件是異步的(類似 new thread 操作的)。這時(shí)候可以定一個 XXContext 上下文:
public class XXContext {private static ThreadLocal<Map<Class<?>, Object>> context = new InheritableThreadLocal<>();/*** 把參數(shù)設(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àn)橥粋€請求線程可能被業(yè)務(wù)方公用。
(完)
轉(zhuǎn)載于:https://www.cnblogs.com/Alandre/p/11145516.html
總結(jié)
以上是生活随笔為你收集整理的真实项目中 ThreadLocal 的妙用的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MySQL中间件之ProxySQL(13
- 下一篇: Apache Doris编译安装记录