ThreadLocal分析
ThreadLocal
本文以JDK21為例子,其實大致方法和JDK8都一樣。
1.基本介紹
ThreadLocal 是一個在多線程編程中常用的概念,不同編程語言中實現方式不同,但核心思想一致:為每個使用該變量的線程都提供一個獨立的變量副本,每個線程都可以獨立地改變自己的副本,而不會影響其他線程所對應的副本。
主要作用
- 線程安全:避免多線程共享變量時需要進行同步操作(如加鎖),從而簡化并發編程。
- 傳遞上下文:在同一個線程的不同方法中傳遞數據,避免顯式傳遞參數。
它的幾個API:
| 方法聲明 | 描述 |
|---|---|
| ThreadLocal() | 創建ThreadLocal對象 |
| public void set(T value) | 設置當前線程綁定的局部變量 |
| public T get() | 獲取當前線程綁定的局部變量 |
| public void remove() | 移除當前線程綁定的局部變量 |
下面來簡單使用一下:
public class SimpleLocalTest {
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
threadLocal.set("main" + "變量");
new Thread(() -> {
// 在線程1中設置變量
threadLocal.set("thread1" + "變量");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 在線程1中得到的仍然是該變量的值,并沒有得到其他線程的值,達到了線程間數據隔離
System.out.println(Thread.currentThread().getName() + " : " + threadLocal.get());
threadLocal.remove(); // 刪除掉
}, "線程1").start();
new Thread(() -> {
threadLocal.set("thread2" + "變量"); // 同理
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " : " + threadLocal.get());
threadLocal.remove();
}, "線程2").start();
// 主線程的本地變量值
System.out.println(Thread.currentThread().getName() + " : " + threadLocal.get());
threadLocal.remove();
}
}
2.對比Synchronized
ThreadLocal 和 Synchronized(或其他同步機制)都用于處理多線程環境下的并發問題,但它們的核心思路和應用場景完全不同。
| ThreadLocal | Synchronized |
|---|---|
| 避免共享:為每個線程創建獨立的變量副本,線程之間互不干擾。 | 控制共享:通過鎖機制保證多線程對共享資源的有序訪問。 |
| 空間換時間:每個線程單獨存儲數據,犧牲內存換取無鎖的高效。 | 時間換空間:通過阻塞其他線程的訪問,保證共享資源的安全性。 |
每個線程內部通過 ThreadLocalMap 存儲自己的變量副本,鍵是 ThreadLocal 對象,值是變量值。 |
基于 JVM 內置鎖(Monitor)實現,通過鎖的獲取和釋放控制代碼塊或方法的訪問權限。 |
通過 get()/set() 直接操作當前線程的局部變量,無需鎖。 |
鎖競爭時,未獲取鎖的線程會進入阻塞狀態(或自旋),直到鎖釋放。 |
| 線程隔離:每個線程需要獨立操作變量(如用戶會話、數據庫連接)。 | 共享資源保護:多個線程需要操作同一資源(如計數器、緩存)。 |
避免線程安全問題:通過隔離變量副本,無需同步(如 SimpleDateFormat)。 |
原子性保證:確保一段代碼的原子執行(如余額扣減)。 |
| 性能敏感場景:避免鎖競爭的開銷(如線程池中的上下文傳遞)。 | 臨界區保護:保護共享數據的讀寫一致性。 |
內存泄漏風險:若未調用 remove(),線程池中的線程可能因 ThreadLocalMap 的強引用導致內存泄漏。 |
性能開銷:鎖競爭激烈時,線程阻塞和喚醒會帶來性能損耗(尤其是重量級鎖)。 |
無鎖操作:get()/set() 直接操作線程私有數據,性能極高。 |
鎖優化:JVM 對 synchronized 有鎖升級機制(偏向鎖→輕量級鎖→重量級鎖)。 |
- 兩者可以結合使用:例如用
ThreadLocal保存線程私有數據【數據隔離】,用Synchronized保護共享狀態【數據共享】。 - 現代框架中的典型應用:Spring 的事務管理通過
ThreadLocal保存數據庫連接
3.原理分析
首先從上面的ThreadLocal簡單使用案例的方法來看看。
①set
// 首先是構造方法
public ThreadLocal() {} // 沒啥好看的
// 然后是set方法
public void set(T value) {
//public static native Thread currentThread();這是個native方法
//currentThread方法返回正在被執行的線程的信息。
set(Thread.currentThread(), value);
if (TRACE_VTHREAD_LOCALS) { // 這個就不用看了
dumpStackIfVirtualThread();
}
}
private void set(Thread t, T value) {
ThreadLocalMap map = getMap(t); // 調用getMap方法
if (map != null) {
// key 是Thread
map.set(this, value); // 如果map不是null,就把kv設置進去
} else {
// 創建map
createMap(t, value);
}
}
// 返回Thread的threadLocals變量
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
// 把Thread的這個變量初始化
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
// ThreadLocalMap的構造方法
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
// 初始化哈希表,然后把第一個kv放進去
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
經過上面的源碼分析我們可以得出以下信息:
- 每個Thread對象里面都有一個threadLocals變量,它的類型是ThreadLocalMap;
- ThreadLocalMap是ThreadLocal的靜態內部類
下面來簡單看一下ThreadLocalMap(ThreadLocal的靜態內部類)
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
private static final int INITIAL_CAPACITY = 16;
private Entry[] table;
private int size = 0;
private int threshold;
// set
// getEntry【返回hash索引位置上的對象Entry】
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.refersTo(key))
return e;
else
return getEntryAfterMiss(key, i, e);
}
.......
}
可以把它看作為一個Map。到這里,set方法算是知道了,然后還大致知道了他們之間的關系,如下圖:【不同線程之間的ThreadLocal他們的value是不一樣的,達到了線程隔離的效果】

②get
public T get() {
return get(Thread.currentThread());
}
private T get(Thread t) {
//getMap在面已經知道了
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T) e.value;
return result;
}
}
// 如果t.threadLocals == null
return setInitialValue(t);
}
// 初始化
private T setInitialValue(Thread t) {
T value = initialValue(); // 調用這個
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
if (this instanceof TerminatingThreadLocal<?> ttl) {
TerminatingThreadLocal.register(ttl);
}
if (TRACE_VTHREAD_LOCALS) {
dumpStackIfVirtualThread();
}
return value;
}
/*
此方法的作用是返回該線程局部變量的初始值。
- 這個方法是一個延遲調用方法,從上面的代碼我們得知,在set方法還未調用而先調用了get方法時才執行,并且僅執行1次。
- 這個方法直接返回一個null。
- 如果想要一個除null之外的初始值,可以重寫此方法。(該方法是一個protected的方法,顯然是為了讓子類覆蓋而設計的)
*/
protected T initialValue() {
return null;
}
get方法就是首先拿到執行方法的線程是哪一個,然后從該線程的threadLocals(ThreadLocalMap)上以該Thread對象為key,拿到Entry的value值。
③remove
public void remove() {
remove(Thread.currentThread());
}
private void remove(Thread t) {
ThreadLocalMap m = getMap(t);
if (m != null) {
// 實際是ThreadLocalMap的remove方法
m.remove(this);
}
}
// 靜態內部類ThreadLocalMap.java
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.refersTo(key)) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
// 會調用這個
/*
主要用于清理因弱引用導致key為null 的過期Entry,從而避免內存泄漏-見下文
*/
private int expungeStaleEntry(int staleSlot) {
。。。。
}
直接將ThrealLocal 對應的值從當前的Thread中的ThreadLocalMap中刪除
④再說ThreadLocalMap
我們對于ThreadLocal的get、set或者是remove,本質上都是在操作ThreadLocaMap里面的Entry數組
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
Entry將ThreadLocal作為Key【為弱引用】,值作為value保存,它繼承自WeakReference. 這個弱引用是啥?前面還說了,可以把它看作為一個Map(ThreadLocalMap并沒有實現Map接口),但是我們熟知的HashMap之類的,key可能是會沖突的,這里的ThreadLocalMap里面的key沖突了咋辦呢?本節就來分析一下。
1) 沖突
發生沖突的時候,那么肯定是在set/get的時候把,我們看一下set方法
// 靜態內部類ThreadLocalMap.java
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
/*
這里的nextIndex如下【說實話就是 i+1】
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
*/
for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
if (e.refersTo(key)) { // 如果key相同
e.value = value; // value直接覆蓋
return;
}
//如果當前位置是空的,就初始化一個Entry對象放在位置i上
if (e.refersTo(null)) {
replaceStaleEntry(key, value, i);
return;
}
}
// 找到了下標為null的位置
tab[i] = new Entry(key, value); // 這個位置設置上key,value
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
根據上面的源碼分析,我們得知,set的時候,根據ThreadLocal對象的hash值,定位到table中的位置i,然后判斷該位置是否為空;如果key相同,直接覆蓋舊值;如果是空的,初始化一個Entry對象放在位置i上;否則,就在i的位置上,往后一個一個找。【線性探測法】
再來看一下get方法:
// 靜態內部類ThreadLocalMap.java
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.refersTo(key))
return e;
else
return getEntryAfterMiss(key, i, e);
}
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
// 相等就直接返回,不相等就繼續+1查找,找到相等為止。
while (e != null) {
if (e.refersTo(key))
return e;
if (e.refersTo(null))
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
上面的源碼很簡單吧。可以知道,ThreadLocal在hash沖突嚴重的時候,他的效率其實是不高的。
2) 弱引用
那么這個弱引用呢?說起這個,肯定會提到ThreadLocal老生常談的內存泄漏問題了。
內存泄漏:【不會再被使用的對象或者變量占用的內存不能被回收,就是內存泄露。】
Memory overflow:內存溢出,沒有足夠的內存提供申請者使用。
Memory leak:內存泄漏是指程序中己動態分配的堆內存由于某種原因程序未釋放或無法釋放,造成系統內存的浪費,導致程序運行速度減慢甚至系統潰等嚴重后果。內存泄漏的堆積終將導致內存溢出。
在 Java 中,弱引用(Weak Reference) 是一種特殊的引用類型,它的核心特點是:當垃圾回收(GC)發生時,無論內存是否充足,弱引用指向的對象【對象必須僅被弱引用指向(沒有任何強引用)】都會被回收。這與其他引用類型(如強引用、軟引用、虛引用)有顯著區別。下面通過對比不同引用類型,詳細解釋弱引用的特性及用途。
| 引用類型 | GC 行為 | 典型應用場景 | 實現類 |
|---|---|---|---|
| 強引用 | 對象有強引用時,永遠不會被回收(如果想取消強引用和某個對象之間的關聯,可以顯式地將引用賦值為null,這樣可以使JVM在合適的時間就會回收該對象。) | 日常對象的使用(如 new Object()) |
默認引用類型 |
| 弱引用 | 只要發生 GC,就會被回收 | 緩存、ThreadLocal 防內存泄漏 |
WeakReference<T> |
| 軟引用 | 內存不足時才會被回收 | 緩存(如圖片緩存) | SoftReference<T> |
| 虛引用 | 無法通過虛引用訪問對象,GC 后會收到通知 | 資源清理跟蹤(如堆外內存釋放) | PhantomReference<T> |
弱引用的回收時機由 GC 決定,無法精確控制。
在弄清楚上述的內存泄漏和弱引用問題之前,我們需要先知道ThreadLocal體系的對象存在哪里的,如下圖:
Thread ThreadRef = new Thread(xx);就是強引用,下圖中用實線連接起來的,ThreadLocal同理。

在 ThreadLocal 的使用中,內存泄漏的核心原因是 ThreadLocalMap 的 Entry 對 value 是強引用,而 Entry 的 key 是對 ThreadLocal 實例的弱引用。當 ThreadLocal 實例失去外部強引用時,GC 會回收 key(弱引用),key就為null了,但 value 仍被強引用保留在 Entry 中,若線程長期存活(如線程池線程),value 將無法被回收,導致內存泄漏。那么,什么時候ThreadLocal會失去外部的強引用呢?下面給出兩個例子
// 下面兩個例子中,線程一直存活
// 1.局部變量場景
public void processRequest() {
ThreadLocal<User> userContext = new ThreadLocal<>(); // 局部變量
userContext.set(currentUser);
// ...業務邏輯...
}
// 另一個線程一直存活
Thread(() -> {
processRequest();
......
}).start();
/*
在上述場景中:
方法結束時,局部變量 userContext 的強引用被釋放。
此時 ThreadLocal 實例僅被 ThreadLocalMap 的弱引用(Entry 的 key)指向。
下次 GC 發生時,ThreadLocal 實例的 key 被回收,Entry 變為 key=null, value=強引用。
若線程持續運行(如線程池線程),value 無法自動回收,導致泄漏。
*/
// 2.實例變量所屬對象被回收
public class Service {
// 在這里,ThreadLocal作為了對象的實例變量
private ThreadLocal<Connection> connHolder = new ThreadLocal<>();
public void execute() {
connHolder.set(getConnection());
// ...使用連接...
}
}
// 使用示例
void process() {
Service service = new Service();
service.execute();
service = null; // Service 實例失去強引用,可能被回收
}
// 另一個線程一直存活
Thread(() -> {
process();
......
}).start();
總結一下,何時出現 “無外部強引用”?
- 當
ThreadLocal實例不再被任何強引用直接或間接指向的時候:- 局部變量超出作用域。
- 所屬對象實例被回收。
- 但線程仍存活(如線程池線程長期復用)。
我們平時開發都是使用的線程池,線程有的可能會一直存活。
為了避免出現上述情況,我們平時使用完成之后,盡量在業務結束并且不需要該線程本地變量的時候,給它remove掉。
通過上述分析,我們可以看到一個非常致命的條件,那就是線程存活的時間 大于了 ThreadLocal的強引用存活時間。如果說,ThreadLocal 和 Thread的生命周期一樣長,即時我們不remove,一樣不會內存泄漏的。( 如下圖 )

ThreadLocal其實它有兜底措施的:【就是上面的remove方法里面調用的expungeStaleEntry】
/*
在這些方法也會調用的
set()方法: 當插入新值時發現哈希沖突,且當前槽位的Entry已過期,觸發清理流程
get()方法: 當查詢Entry未命中(key 不匹配)時,觸發清理以優化后續查詢效率
*/
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// 清理當前槽位
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// Rehash until we encounter null
Entry e;
int i;
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == null) {// 清理過期 Entry
e.value = null;
tab[i] = null;
size--;
} else {// 重新哈希有效 Entry
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
當 ThreadLocal 實例被垃圾回收(GC)后,其對應的 Entry 的 key 會變為 null(弱引用特性),但 value 仍被強引用保留。expungeStaleEntry 會遍歷哈希數組,將這些 key 為 null 的 Entry 的 value 置為 null,并釋放 Entry 對象本身,從而切斷 value 的強引用鏈,幫助 GC 回收內存
刪除的時候:
- 從指定位置開始向后遍歷哈希數組,若發現
Entry的key為null,則清除其value并釋放Entry對象。 - 若
Entry的key有效,則重新計算其哈希值(k.threadLocalHashCode & (len - 1)),檢查是否需要調整位置以優化哈希分布
從上述分析可以看出:expungeStaleEntry 僅清理從指定位置開始的連續過期 Entry,而非整個哈希表,因此無法完全避免內存泄漏;清理效果取決于 set、get 等方法的調用頻率,若線程長期不操作 ThreadLocal,殘留的 value 仍可能堆積。
那如果key是強引用呢?會出現上述內存泄漏問題嗎?

為什么key設計成弱引用呢?
當 ThreadLocalMap 的鍵是 弱引用 時:
- 外部強引用
threadLocal被置為null后,鍵(弱引用)指向的ThreadLocal對象僅被弱引用持有。 - GC 會回收
ThreadLocal對象,并將對應的弱引用放入引用隊列,key就為null了。 ThreadLocalMap在下次操作(如get()、set())時,會清理引用隊列中的失效條目,釋放值對象的內存。【就是我們說的兜底措施嘛】
如果 ThreadLocalMap 的鍵是 強引用,會發生:
threadLocal變量被置為null,但ThreadLocalMap中的鍵仍強引用著ThreadLocal對象。ThreadLocal對象無法被 GC 回收,導致其對應的值(BigObject)也無法被回收,即使線程可能長期存活(如線程池中的線程)。- 內存泄漏:無用的鍵值對會一直存在于
ThreadLocalMap中
5.框架中的應用
這一節只看一下ThreadLocal在Spring事務中的應用。
在事務中,需要做到如下保證:
- 每個事務的執行需要通過數據源連接池獲取到數據庫的connetion。
- 為了保證所有的數據庫操作都屬于同一個事務,事務使用的連接必須是同一個,也就是在一個線程里面需要操作同一個連接
- 線程隔離:在多線程并發的情況下,每個線程都只能操作各自的connetion,不能使用其他線程的連接
在Spring事務的源碼中:
DataSourceTransactionManager.java
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
Connection con = null;
try {
if (!txObject.hasConnectionHolder() ||
txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
Connection newCon = obtainDataSource().getConnection();
...
txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
}
txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
con = txObject.getConnectionHolder().getConnection();
....
// 手動開啟一個事務
if (con.getAutoCommit()) {
txObject.setMustRestoreAutoCommit(true);
...
con.setAutoCommit(false);
}
prepareTransactionalConnection(con, definition);
txObject.getConnectionHolder().setTransactionActive(true);
int timeout = determineTimeout(definition);
if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
}
// Bind the connection holder to the thread.
if (txObject.isNewConnectionHolder()) {
TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
}
}
catch (Throwable ex) {
if (txObject.isNewConnectionHolder()) {
DataSourceUtils.releaseConnection(con, obtainDataSource());
txObject.setConnectionHolder(null, false);
}
throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
}
}
TransactionSynchronizationManager.java :下面就把線程和ThreadLocal綁定起來了。
public abstract class TransactionSynchronizationManager {
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<>("Transactional resources");
....
public static void bindResource(Object key, Object value) throws IllegalStateException {
// 這個其實是數據源dataSource對象
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
Assert.notNull(value, "Value must not be null");
// 拿到當前線程的map
Map<Object, Object> map = resources.get();
// 為空,初始化一下
if (map == null) {
map = new HashMap<>();
resources.set(map);
}
Object oldValue = map.put(actualKey, value);
if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) {
oldValue = null;
}
if (oldValue != null) {
throw new IllegalStateException(
"Already value [" + oldValue + "] for key [" + actualKey + "] bound to thread");
}
}
// 清理remove操作
@Nullable
private static Object doUnbindResource(Object actualKey) {
Map<Object, Object> map = resources.get();
if (map == null) {
return null;
}
Object value = map.remove(actualKey);
// Remove entire ThreadLocal if empty...
if (map.isEmpty()) {
resources.remove();
}
// Transparently suppress a ResourceHolder that was marked as void...
if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
value = null;
}
return value;
}
}
在上面源碼中,每個線程里面的本地變量是一個map,map是以DateSource為key,ConnectionHolder為value。在一個系統可以有多個DataSource,connection又是由相應的DataSource得到的。所以ThreadLocal維護的是以DataSource作為key, 以ConnectionHolder為value的一個Map

總結一下,在開啟事務的時候,綁定資源,Spring 事務管理器(如 DataSourceTransactionManager)會調用 doBegin(),獲取數據庫連接并綁定到 ThreadLocal。
在執行sql的時候,MyBatis 通過 SpringManagedTransaction 獲取當前事務的連接
// SqlSessionUtils.java
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
.....
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}
LOGGER.debug(() -> "Creating a new SqlSession");
session = sessionFactory.openSession(executorType);
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
// TransactionSynchronizationManager.java
@Nullable
public static Object getResource(Object key) {
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
return doGetResource(actualKey);
}
最后提交事務,相關清理工作.... 就是remove那些了。
總流程如下所示:
// TransactionAspectSupport.java
@Nullable
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
final InvocationCallback invocation) throws Throwable {
if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
// 1.綁定連接的ThreadLocal
TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
Object retVal;
try {
// 2.進去往下一步執行-- 執行sql【jdbcTempalte、sqlsessionTemplate....】
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// 異常回滾
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
// 3.清理工作
cleanupTransactionInfo(txInfo);
}
if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) {
// Set rollback-only in case of Vavr failure matching our rollback rules...
TransactionStatus status = txInfo.getTransactionStatus();
if (status != null && txAttr != null) {
retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
}
}
commitTransactionAfterReturning(txInfo);
return retVal;
}
}
end.參考
留一個問題:ThreadLocal還有一個很經典的問題,那就是在父子線程中通信的問題了。
1.https://blog.csdn.net/qq_35190492/article/details/107599875
2.https://blog.csdn.net/u010445301/article/details/111322569

浙公網安備 33010602011771號