大神們是怎麼使用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

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

公眾號
公眾號