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