這篇文章是關於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
關注我的公眾號,和我一起成長~