Java ThreadLocal深度解析

轉載:http://www.codeceo.com/article/java-threadlocal-deep-learn.html

首先,ThreadLocal不是用來解決共享對象的多線程訪問問題的,一般情況下,通過ThreadLocal.set() 到線程中的對象是該線程自己使用的對象,其他線程是不需要訪問的,也訪問不到的。各個線程中訪問的是不同的對象。

另外,說ThreadLocal使得各線程能夠保持各自獨立的一個對象,并不是通過ThreadLocal.set()來實現的,而是通過每個線程中的new 對象 的操作來創建的對象,每個線程創建一個,不是什么對象的拷貝或副本。通過ThreadLocal.set()將這個新創建的對象的引用保存到各線程的自己的一個map中,每個線程都有這樣一個map,執行ThreadLocal.get()時,各線程從自己的map中取出放進去的對象,因此取出來的是各自自己線程中的對象,ThreadLocal實例是作為map的key來使用的。

如果ThreadLocal.set()進去的東西本來就是多個線程共享的同一個對象,那么多個線程的ThreadLocal.get()取得的還是這個共享對象本身,還是有并發訪問問題。

下面來看一個hibernate中典型的ThreadLocal的應用:

private static final ThreadLocal threadSession = new ThreadLocal();  

public static Session getSession() throws InfrastructureException {  
    Session s = (Session) threadSession.get();  
    try {  
        if (s == null) {  
            s = getSessionFactory().openSession();  
            threadSession.set(s);  
        }  
    } catch (HibernateException ex) {  
        throw new InfrastructureException(ex);  
    }  
    return s;  
}

可以看到在getSession()方法中,首先判斷當前線程中有沒有放進去session,如果還沒有,那么通過sessionFactory().openSession()來創建一個session,再將session set到線程中,實際是放到當前線程的ThreadLocalMap這個map中,這時,對于這個session的唯一引用就是當前線程中的那個ThreadLocalMap(下面會講到),而threadSession作為這個值的key,要取得這個session可以通過threadSession.get()來得到,里面執行的操作實際是先取得當前線程中的ThreadLocalMap,然后將threadSession作為key將對應的值取出。這個session相當于線程的私有變量,而不是public的。 顯然,其他線程中是取不到這個session的,他們也只能取到自己的ThreadLocalMap中的東西。要是session是多個線程共享使用的,那還不亂套了。 試想如果不用ThreadLocal怎么來實現呢?可能就要在action中創建session,然后把session一個個傳到service和dao中,這可夠麻煩的?;蛘呖梢宰约憾x一個靜態的map,將當前thread作為key,創建的session作為值,put到map中,應該也行,這也是一般人的想法,但事實上,ThreadLocal的實現剛好相反,它是在每個線程中有一個map,而將ThreadLocal實例作為key,這樣每個map中的項數很少,而且當線程銷毀時相應的東西也一起銷毀了,不知道除了這些還有什么其他的好處。

總之,ThreadLocal不是用來解決對象共享訪問問題的,而主要是提供了保持對象的方法和避免參數傳遞的方便的對象訪問方式。歸納了兩點: 1。每個線程中都有一個自己的ThreadLocalMap類對象,可以將線程自己的對象保持到其中,各管各的,線程可以正確的訪問到自己的對象。 2。將一個共用的ThreadLocal靜態實例作為key,將不同對象的引用保存到不同線程的ThreadLocalMap中,然后在線程執行的各處通過這個靜態ThreadLocal實例的get()方法取得自己線程保存的那個對象,避免了將這個對象作為參數傳遞的麻煩。

當然如果要把本來線程共享的對象通過ThreadLocal.set()放到線程中也可以,可以實現避免參數傳遞的訪問方式,但是要注意get()到的是那同一個共享對象,并發訪問問題要靠其他手段來解決。但一般來說線程共享的對象通過設置為某類的靜態變量就可以實現方便的訪問了,似乎沒必要放到線程中。

ThreadLocal的應用場合,我覺得最適合的是按線程多實例(每個線程對應一個實例)的對象的訪問,并且這個對象很多地方都要用到。

下面來看看ThreadLocal的實現原理(jdk1.5源碼)

public class ThreadLocal<T> {  
    /** 
     * ThreadLocals rely on per-thread hash maps attached to each thread 
     * (Thread.threadLocals and inheritableThreadLocals).  The ThreadLocal 
     * objects act as keys, searched via threadLocalHashCode.  This is a 
     * custom hash code (useful only within ThreadLocalMaps) that eliminates 
     * collisions in the common case where consecutively constructed 
     * ThreadLocals are used by the same threads, while remaining well-behaved 
     * in less common cases. 
     */  
    private final int threadLocalHashCode = nextHashCode();  

    /** 
     * The next hash code to be given out. Accessed only by like-named method. 
     */  
    private static int nextHashCode = 0;  

    /** 
     * The difference between successively generated hash codes - turns 
     * implicit sequential thread-local IDs into near-optimally spread 
     * multiplicative hash values for power-of-two-sized tables. 
     */  
    private static final int HASH_INCREMENT = 0x61c88647;  

    /** 
     * Compute the next hash code. The static synchronization used here 
     * should not be a performance bottleneck. When ThreadLocals are 
     * generated in different threads at a fast enough rate to regularly 
     * contend on this lock, memory contention is by far a more serious 
     * problem than lock contention. 
     */  
    private static synchronized int nextHashCode() {  
        int h = nextHashCode;  
        nextHashCode = h + HASH_INCREMENT;  
        return h;  
    }  

    /** 
     * Creates a thread local variable. 
     */  
    public ThreadLocal() {  
    }  

    /** 
     * Returns the value in the current thread's copy of this thread-local 
     * variable.  Creates and initializes the copy if this is the first time 
     * the thread has called this method. 
     * 
     * @return the current thread's value of this thread-local 
     */  
    public T get() {  
        Thread t = Thread.currentThread();  
        ThreadLocalMap map = getMap(t);  
        if (map != null)  
            return (T)map.get(this);  

        // Maps are constructed lazily.  if the map for this thread  
        // doesn't exist, create it, with this ThreadLocal and its  
        // initial value as its only entry.  
        T value = initialValue();  
        createMap(t, value);  
        return value;  
    }  

    /** 
     * Sets the current thread's copy of this thread-local variable 
     * to the specified value.  Many applications will have no need for 
     * this functionality, relying solely on the {@link #initialValue} 
     * method to set the values of thread-locals. 
     * 
     * @param value the value to be stored in the current threads' copy of 
     *        this thread-local. 
     */  
    public void set(T value) {  
        Thread t = Thread.currentThread();  
        ThreadLocalMap map = getMap(t);  
        if (map != null)  
            map.set(this, value);  
        else  
            createMap(t, value);  
    }  

    /** 
     * Get the map associated with a ThreadLocal. Overridden in 
     * InheritableThreadLocal. 
     * 
     * @param  t the current thread 
     * @return the map 
     */  
    ThreadLocalMap getMap(Thread t) {  
        return t.threadLocals;  
    }  

    /** 
     * Create the map associated with a ThreadLocal. Overridden in 
     * InheritableThreadLocal. 
     * 
     * @param t the current thread 
     * @param firstValue value for the initial entry of the map 
     * @param map the map to store. 
     */  
    void createMap(Thread t, T firstValue) {  
        t.threadLocals = new ThreadLocalMap(this, firstValue);  
    }  

    .......  

    /** 
     * ThreadLocalMap is a customized hash map suitable only for 
     * maintaining thread local values. No operations are exported 
     * outside of the ThreadLocal class. The class is package private to 
     * allow declaration of fields in class Thread.  To help deal with 
     * very large and long-lived usages, the hash table entries use 
     * WeakReferences for keys. However, since reference queues are not 
     * used, stale entries are guaranteed to be removed only when 
     * the table starts running out of space. 
     */  
    static class ThreadLocalMap {  

    ........  

    }  

}

可以看到ThreadLocal類中的變量只有這3個int型:

private final int threadLocalHashCode = nextHashCode();  
private static int nextHashCode = 0;  
private static final int HASH_INCREMENT = 0x61c88647;

而作為ThreadLocal實例的變量只有 threadLocalHashCode 這一個,nextHashCode 和HASH_INCREMENT 是ThreadLocal類的靜態變量,實際上HASH_INCREMENT是一個常量,表示了連續分配的兩個ThreadLocal實例的threadLocalHashCode值的增量,而nextHashCode 的表示了即將分配的下一個ThreadLocal實例的threadLocalHashCode 的值。

可以來看一下創建一個ThreadLocal實例即new ThreadLocal()時做了哪些操作,從上面看到構造函數ThreadLocal()里什么操作都沒有,唯一的操作是這句:

private final int threadLocalHashCode = nextHashCode();

那么nextHashCode()做了什么呢:

private static synchronized int nextHashCode() {  
    int h = nextHashCode;  
    nextHashCode = h + HASH_INCREMENT;  
    return h;  
}

就是將ThreadLocal類的下一個hashCode值即nextHashCode的值賦給實例的threadLocalHashCode,然后nextHashCode的值增加HASH_INCREMENT這個值。

因此ThreadLocal實例的變量只有這個threadLocalHashCode,而且是final的,用來區分不同的ThreadLocal實例,ThreadLocal類主要是作為工具類來使用,那么ThreadLocal.set()進去的對象是放在哪兒的呢?

看一下上面的set()方法,兩句合并一下成為

ThreadLocalMap map = Thread.currentThread().threadLocals;

這個ThreadLocalMap 類是ThreadLocal中定義的內部類,但是它的實例卻用在Thread類中:

public class Thread implements Runnable {  
    ......  

    /* ThreadLocal values pertaining to this thread. This map is maintained 
     * by the ThreadLocal class. */  
    ThreadLocal.ThreadLocalMap threadLocals = null;    
    ......  
}

再看這句:

if (map != null)  
    map.set(this, value);

也就是將該ThreadLocal實例作為key,要保持的對象作為值,設置到當前線程的ThreadLocalMap 中,get()方法同樣大家看了代碼也就明白了。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,578評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,701評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,691評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,974評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,694評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,026評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,015評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,193評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,719評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,442評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,668評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,151評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,846評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,255評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,592評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,394評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內容

  • 從三月份找實習到現在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發崗...
    時芥藍閱讀 42,339評論 11 349
  • 前言 ThreadLocal很多同學都搞不懂是什么東西,可以用來干嘛。但面試時卻又經常問到,所以這次我和大家一起學...
    liangzzz閱讀 12,485評論 14 228
  • Android Handler機制系列文章整體內容如下: Android Handler機制1之ThreadAnd...
    隔壁老李頭閱讀 7,665評論 4 30
  • 原創文章&經驗總結&從校招到A廠一路陽光一路滄桑 詳情請戳www.codercc.com 1. ThreadLoc...
    你聽___閱讀 6,758評論 8 19
  • 小心翼翼的圈養著自己 封閉在三寸半虛擬的手機 瀏覽著QQ群微信陌陌 音樂成了我孤獨的伴侶 也曾想出去瘋狂幾下 經濟...
    壟上行云閱讀 223評論 0 3