大神們是怎麼使用ThreadLocal的?

語言: CN / TW / HK

這篇文章是關於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,一個有顏有料又有趣的程式設計師。

微信公眾號:編了個程

個人網站:http://yasinshaw.com

關注我的公眾號,和我一起成長~

公眾號
公眾號