這篇文章是關於ThreadLocal的第三篇文章。本文將挑選一些主流的Java開源框架,從原始碼上分析,大神們是如何使用ThreadLocal的,學習他們的設計思想。
❝大家可以直接開啟github,搜尋相應的專案,然後在專案中搜索相關的類,即可看到原始碼。
❞
Quartz
Quartz是一個非常知名的開源任務排程系統。
我們要看的原始碼是Quartz的SimpleSemaphore
這個類。它是一個訊號量的實現,在生產者-消費者模型裡,訊號量代表的就是佇列裡有多少item需要處理。
在訊號量的模型裡面有一個“等待”操作。當消費者消費完後,會輪詢等待。SimpleSemaphore
有一個獲取鎖的方法obtainLock()
,我們要看的也是這個方法的內部程式碼:
92行的while迴圈就是去進行輪詢操作,while裡面的locks是一個HashSet
,為true代表這個lockName對應的鎖正在被別的執行緒持有,所以當前執行緒需要等待。
我們看到,在while迴圈的外層86行,有一個判斷,其實是用到了ThreadLocal。
這個外層的判斷起什麼作用呢?其實是「判斷當前執行緒是否已經持有了這個鎖」。如果持有了,那就直接跳到最後return true了。因為同一個執行緒,可能有多個程式片段會呼叫這個獲取鎖的方法。
可以看到,使用ThreadLocal可以非常高效地判斷當前執行緒的狀態,可以快速檢測出當前執行緒是否已經獲取了鎖,避免了後續鎖的檢測和爭用。
Mybatis
Mybatis不用多說,搞Java的應該都聽過或者用過。我們今天要介紹的是它的SqlSessionManager。
Mybatis是一個持久化框架。持久化框架,必然會面臨事務的問題。我們的資料庫(比如MySQL)可以保證本地事務,但也要求必須在同一個連線才行。
應用程式使用MyBatis,可能會在多個程式片段去訪問資料庫,做一些增刪改查的操作。它們可能需要在同一個事務裡面。
舉個例子,我們修改完訂單狀態後,可能還需要修改積分,它們應該在同一個事務裡。
Mybatis使用SqlSessionManager保證了我們同一個執行緒取出來的連線總是同一個。它是如何做到的呢?其實很簡單,就是內部使用了一個ThreadLocal。
然後所有的建立連線、取連線都是通過這個ThreadLocal變數的get/set方法進行操作。
private final ThreadLocal<SqlSession> localSqlSession = new ThreadLocal<>();
// 建立連線
public void startManagedSession() {
this.localSqlSession.set(openSession());
}
// 取連線
@Override
public Connection getConnection() {
final SqlSession sqlSession = localSqlSession.get();
if (sqlSession == null) {
throw new SqlSessionException("Error: Cannot get connection. No managed session is started.");
}
return sqlSession.getConnection();
}
複製程式碼
總結
其實ThreadLocal使用起來是很簡單的,這也是ThreadLocal設計的初衷。
使用ThreadLocal,可以儲存執行緒的狀態,使得多個程式片段可以很方便地得到當前執行緒的資料,而不會對其它執行緒造成影響,也不需要上鎖同步。
所以,使用ThreadLocal可以“避免”一些多執行緒問題,開發安全高效的應用程式。
關於作者
我是Yasin,一個有顏有料又有趣的程式設計師。
微信公眾號:編了個程
個人網站:https://yasinshaw.com
關注我的公眾號,和我一起成長~