深刻JDK源碼之ThreadLocal類

##ThreadLocal概述## 學習JDK中的類,首先看下JDK API對此類的描述,描述以下: 該類提供了線程局部 (thread-local) 變量。這些變量不一樣於它們的普通對應物,由於訪問某個變量(經過其 get 或 set 方法)的每一個線程都有本身的局部變量,它獨立於變量的初始化副本。ThreadLocal其實就是一個工具類,用來操做線程局部變量,ThreadLocal 實例一般是類中的 private static 字段。它們但願將狀態與某一個線程(例如,用戶 ID 或事務 ID)相關聯。 例如,如下類生成對每一個線程惟一的局部標識符。線程 ID 是在第一次調用UniqueThreadIdGenerator.getCurrentThreadId()時分配的,在後續調用中不會更改。java

其實ThreadLocal並不是是一個線程的本地實現版本,它並非一個Thread,而是threadlocalvariable(線程局部變量)。也許把它命名爲ThreadLocalVar更加合適。線程局部變量(ThreadLocal)其實的功用很是簡單,就是爲每個使用該變量的線程都提供一個變量值的副本,是Java中一種較爲特殊的線程綁定機制,是每個線程均可以獨立地改變本身的副本,而不會和其它線程的副本衝突。python

import java.util.concurrent.atomic.AtomicInteger;

public class UniqueThreadIdGenerator {
 
     private static final AtomicInteger uniqueId = new AtomicInteger(0);
 
     private static final ThreadLocal < Integer > uniqueNum = 
         new ThreadLocal < Integer > () {
             @Override protected Integer initialValue() {
                 return uniqueId.getAndIncrement();
         }
     };
  
     public static int getCurrentThreadId() {
         return uniqueId.get();
     }
}

從線程的角度看,每一個線程都保持對其線程局部變量副本的隱式引用,只要線程是活動的而且 ThreadLocal 實例是可訪問的;在線程消失以後,其線程局部實例的全部副本都會被垃圾回收(除非存在對這些副本的其餘引用)。web

API表達了下面幾種觀點:算法

  1. ThreadLocal不是線程,是線程的一個變量,你能夠先簡單理解爲線程類的屬性變量。
  2. ThreadLocal 在類中一般定義爲靜態類變量。
  3. 每一個線程有本身的一個ThreadLocal,它是變量的一個‘拷貝’,修改它不影響其餘線程。

既然定義爲類變量,爲什麼爲每一個線程維護一個副本(姑且成爲‘拷貝’容易理解),讓每一個線程獨立訪問?多線程編程的經驗告訴咱們,對於線程共享資源(你能夠理解爲屬性),資源是否被全部線程共享,也就是說這個資源被一個線程修改是否影響另外一個線程的運行,若是影響咱們須要使用synchronized同步,讓線程順序訪問。數據庫

ThreadLocal適用於資源共享但不須要維護狀態的狀況,也就是一個線程對資源的修改,不影響另外一個線程的運行;這種設計是空間換時間,synchronized順序執行是時間換取空間apache

##ThreadLocal介紹##編程

從字面上來理解ThreadLocal,感受就是至關於線程本地的。咱們都知道,每一個線程在jvm的虛擬機裏都分配有本身獨立的空間,線程之間對於本地的空間是相互隔離的。那麼ThreadLocal就應該是該線程空間裏本地能夠訪問的數據了。ThreadLocal變量高效地爲每一個使用它的線程提供單獨的線程局部變量值的副本。每一個線程只能看到與本身相聯繫的值,而不知作別的線程可能正在使用或修改它們本身的副本。數組

不少人看到這裏會容易產生一種錯誤的印象,感受是否是這個ThreadLocal對象創建了一個相似於全局的map,而後每一個線程做爲map的key來存取對應線程本地的value。你看,每一個線程不同,因此他們映射到map中的key應該也不同。實際上,若是咱們後面詳細分析ThreadLocal的代碼時,會發現不是這樣的。它具體是怎麼實現的呢? 在此輸入圖片描述緩存

##ThreadLocal源碼## ThreadLocal類自己定義了有get(), set()和initialValue()三個方法。前面兩個方法是public的,initialValue()是protected的,主要用於咱們在定義ThreadLocal對象的時候根據須要來重寫。這樣咱們初始化這麼一個對象在裏面設置它的初始值時就用到這個方法。ThreadLocal變量由於自己定位爲要被多個線程來訪問,它一般被定義爲static變量安全

ThreadLocal有一個ThreadLocalMap靜態內部類,你能夠簡單理解爲一個MAP,這個Map爲每一個線程複製一個變量的‘拷貝’存儲其中。

當線程調用ThreadLocal.get()方法獲取變量時,首先獲取當前線程引用,以此爲key去獲取響應的ThreadLocalMap,若是此‘Map’不存在則初始化一個,不然返回其中的變量,代碼以下:

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
 }

調用get方法若是此Map不存在首先初始化,建立此map,將線程爲key,初始化的vlaue存入其中,注意此處的initialValue,咱們能夠覆蓋此方法,在首次調用時初始化一個適當的值。setInitialValue代碼以下:

private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

set方法相對比較簡單若是理解以上倆個方法,獲取當前線程的引用,從map中獲取該線程對應的map,若是map存在更新緩存值,不然建立並存儲,代碼以下:

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

對於ThreadLocal在何處存儲變量副本,咱們看getMap方法:獲取的是當前線程的ThreadLocal類型的threadLocals屬性。顯然變量副本存儲在每個線程中。

/**
 * 獲取線程的ThreadLocalMap 屬性實例
 */
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
}

上面咱們知道變量副本存放於何處,這裏咱們簡單說下如何被java的垃圾收集機制收集,當咱們不在使用時調用set(null),此時不在將引用指向該‘map’,而線程退出時會執行資源回收操做,將申請的資源進行回收,其實就是將屬性的引用設置爲null。這時已經不在有任何引用指向該map,故而會被垃圾收集。

注意:若是ThreadLocal.set()進去的東西原本就是多個線程共享的同一個對象,那麼多個線程的ThreadLocal.get()取得的仍是這個共享對象自己,仍是有併發訪問問題。

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

/**
     * ThreadLocals rely on per-thread linear-probe 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. Updated atomically. Starts at
     * zero.
     */
    private static AtomicInteger nextHashCode = new AtomicInteger();

    /**
     * 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;

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

如今來看看它的哈希策略。全部ThreadLocal對象共享一個AtomicInteger對象nextHashCode用於計算hashcode,一個新對象產生時它的hashcode就肯定了,算法是從0開始,以HASH_INCREMENT = 0x61c88647爲間隔遞增,這是ThreadLocal惟一須要同步的地方。根據hashcode定位桶的算法是將其與數組長度-1進行與操做:key.threadLocalHashCode & (table.length - 1)

0x61c88647這個魔數是怎麼肯定的呢? ThreadLocalMap的初始長度爲16,每次擴容都增加爲原來的2倍,即它的長度始終是2的n次方,上述算法中使用0x61c88647可讓hash的結果在2的n次方內儘量均勻分佈,減小衝突的機率。

能夠來看一下建立一個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;
    
        /*
         * InheritableThreadLocal values pertaining to this thread. This map is
         * maintained by the InheritableThreadLocal class.
         */
        ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
        ......
    }

這就說明了其實每一個Thread自己就包含了兩個ThreadLocalMap對象的引用。這一點很是重要。之後每一個thread要訪問他們的local對象時,就是訪問存在這個ThreadLocalMap裏的value。

ThreadLocalMap是定義在ThreadLocal類內部的私有類,它是採用「開放定址法」解決衝突的hashmap。key是ThreadLocal對象。當調用某個ThreadLocal對象的get或put方法時,首先會從當前線程中取出ThreadLocalMap,而後查找對應的value:

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);     //拿到當前線程的ThreadLocalMap
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);    // 以該ThreadLocal對象爲key取value
        if (e != null)
            return (T)e.value;
    }
    return setInitialValue();
}
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

再看這句:

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

也就是將該ThreadLocal實例做爲key,要保持的對象做爲值,設置到當前線程的ThreadLocalMap 中,get()方法一樣你們看了代碼也就明白了,ThreadLocalMap 類的代碼太多了,我就不帖了,本身去看源碼吧。 ###天然想法實現### 一個很是天然想法是用一個線程安全的 Map<Thread,Object> 實現:

class ThreadLocal { 
  private Map values = Collections.synchronizedMap(new HashMap());

  public Object get() {
    Thread curThread = Thread.currentThread();
    Object o = values.get(curThread);
    if (o == null && !values.containsKey(curThread)) {
      o = initialValue();
      values.put(curThread, o);
    }
    return o;
  }

  public void set(Object newValue) {
    values.put(Thread.currentThread(), newValue);
  }
}

但這是很是naive的:

  1. ThreadLocal本意是避免併發,用一個全局Map顯然違背了這一初衷;
  2. 用Thread當key,除非手動調用remove,不然即便線程退出了會致使:1)該Thread對象沒法回收;2)該線程在全部ThreadLocal中對應的value也沒法回收。

JDK 的實現恰好是反過來的: 在此輸入圖片描述

###碰撞解決與神奇的0x61c88647### 既然ThreadLocal用map就避免不了衝突的產生。

碰撞避免和解決 這裏碰撞其實有兩種類型: (1)只有一個ThreadLocal實例的時候(上面推薦的作法),當向thread-local變量中設置多個值的時產生的碰撞,碰撞解決是經過開放定址法, 且是線性探測(linear-probe)。 (2)多個ThreadLocal實例的時候,最極端的是每一個線程都new一個ThreadLocal實例,此時利用特殊的哈希碼0x61c88647大大下降碰撞的概率, 同時利用開放定址法處理碰撞。

神奇的0x61c88647 注意 0x61c88647 的利用主要是爲了多個ThreadLocal實例的狀況下用的。從ThreadLocal源碼中找出這個哈希碼所在的地方:

/**
 * ThreadLocals rely on per-thread linear-probe 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. Updated atomically.
 * Starts at zero.
 */
private static AtomicInteger nextHashCode = new AtomicInteger();

/**
 * 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;

/**
 * Returns the next hash code.
 */
private static int nextHashCode() {
    return nextHashCode.getAndAdd(HASH_INCREMENT); 
}

注意實例變量threadLocalHashCode, 每當建立ThreadLocal實例時這個值都會累加 0x61c88647, 目的在上面的註釋中已經寫的很清楚了:爲了讓哈希碼能均勻的分佈在2的N次方的數組裏, 即 Entry[] table

下面來看一下ThreadLocal怎麼使用的這個 threadLocalHashCode 哈希碼的,下面是ThreadLocalMap靜態內部類中的set方法的部分代碼:

// Set the value associated with key.
private void set(ThreadLocal key, Object value) {

    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);

    for (Entry e = tab[i]; e != null;
      e = tab[i = nextIndex(i, len)]) {...}

    ...

key.threadLocalHashCode & (len-1)這麼用是什麼意思? 先看一下table數組的長度吧:

/**
 * The table, resized as necessary.
 * table.length MUST always be a power of two.
 */
private Entry[] table;

哇,ThreadLocalMap 中 Entry[] table 的大小必須是2的N次方呀(len = 2^N),那 len-1 的二進制表示就是低位連續的N個1, 那 key.threadLocalHashCode & (len-1) 的值就是 threadLocalHashCode 的低N位, 這樣就能均勻的產生均勻的分佈? 我用python作個實驗吧:

>>> HASH_INCREMENT = 0x61c88647
>>> def magic_hash(n):
...     for i in range(n):
...         nextHashCode = i * HASH_INCREMENT + HASH_INCREMENT
...         print nextHashCode & (n - 1),
...     print
... 
>>> magic_hash(16)
7 14 5 12 3 10 1 8 15 6 13 4 11 2 9 0
>>> magic_hash(32)
7 14 21 28 3 10 17 24 31 6 13 20 27 2 9 16 23 30 5 12 19 26 1 8 15 22 29 4 11 18 25 0

產生的哈希碼分佈真的是很均勻,並且沒有任何衝突啊, 太神奇了。

##ThreadLocal內存泄漏## 不少人認爲:threadlocal裏面使用了一個存在弱引用的map,當釋放掉threadlocal的強引用之後,map裏面的value卻沒有被回收.而這塊value永遠不會被訪問到了. 因此存在着內存泄露. 最好的作法是將調用threadlocal的remove方法。

說的也比較正確,當value再也不使用的時候,調用remove的確是很好的作法.但內存泄露一說卻不正確. 這是threadlocal的設計的不得已而爲之的問題. 首先,讓咱們看看在threadlocal的生命週期中,都存在哪些引用吧. 看下圖: 實線表明強引用,虛線表明弱引用。 在此輸入圖片描述

每一個thread中都存在一個map, map的類型是ThreadLocal.ThreadLocalMap. Map中的key爲一個threadlocal實例. 這個Map的確使用了弱引用,不過弱引用只是針對key. 每一個key都弱引用指向threadlocal. 當把threadlocal實例tl置爲null之後,沒有任何強引用指向threadlocal實例,因此threadlocal將會被gc回收. 可是,咱們的value卻不能回收,由於存在一條從current thread鏈接過來的強引用. 只有當前thread結束之後, current thread就不會存在棧中,強引用斷開, Current Thread, Map, value將所有被GC回收。經過源碼看下此處的實現,以下:

public void set(T value) {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value); // 將當前threadLocal實例做爲key
            else
                createMap(t, value);
        }

        private void set(ThreadLocal key, Object value) {

            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.

            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);

            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal k = e.get();

                if (k == key) {
                    e.value = value;
                    return;
                }

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value); // 構造key-value實例
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

        static class Entry extends WeakReference<ThreadLocal> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal k, Object v) {
                super(k); // 構造key弱引用
                value = v;
            }
        }

        public T get() {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null) {
                ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null)
                    return (T)e.value;
            }
            return setInitialValue();
        }
    
        ThreadLocalMap getMap(Thread t) {
            return t.threadLocals;
        }

從中能夠看出,弱引用只存在於key上,因此key會被回收. 而value還存在着強引用.只有thead退出之後,value的強引用鏈條纔會斷掉。一旦某個ThreadLocal對象沒有強引用了,它在全部線程內部的ThreadLocalMap中的key都將被GC掉(此時value還未回收),在map後續的get/set中會探測到key被回收的entry,將其 value 設置爲 null 以幫助GC,所以 value 在 key 被 GC 後可能還會存活一段時間,但最終也會被回收。這個過程和java.util.WeakHashMap的實現幾乎是同樣的。

所以ThreadLocal自己是沒有內存泄露問題的,一般由它引起的內存泄露問題都是線程只 put 而忘了 remove 致使的,從上面分析可知,即便線程退出了,只要 ThreadLocal 還有強引用,該線程曾經 put 過的東西是不會被回收掉的。 ##ThreadLocal有何用## 不少時候咱們會建立一些靜態域來保存全局對象,那麼這個對象就可能被任意線程訪問到,若是它是線程安全的,這固然沒什麼說的。然而大部分狀況下它不是線程安全的(或者沒法保證它是線程安全的),尤爲是當這個對象的類是由咱們本身(或身邊的同事)建立的(不少開發人員對線程的知識都是隻知其一;不知其二,更況且線程安全)。

這時候咱們就須要爲每一個線程都建立一個對象的副本。咱們固然能夠用ConcurrentMap<Thread, Object>來保存這些對象,但問題是當一個線程結束的時候咱們如何刪除這個線程的對象副本呢?

ThreadLocal爲咱們作了一切。首先咱們聲明一個全局的ThreadLocal對象(final static,沒錯,我很喜歡final),當咱們建立一個新線程並調用threadLocal.get時,threadLocal會調用initialValue方法初始化一個對象並返回,之後不管什麼時候咱們在這個線程中調用get方法,都將獲得同一個對象(除非期間set過)。而若是咱們在另外一個線程中調用get,將的到另外一個對象,並且始終會獲得這個對象。

當一個線程結束了,ThreadLocal就會釋放跟這個線程關聯的對象,這不須要咱們關心,反正一切都悄悄地發生了。

(以上敘述只關乎線程,而不關乎get和set是在哪一個方法中調用的。之前有不少不理解線程的同窗老是問我這個方法是哪一個線程,那個方法是哪一個線程,我不知如何回答。)

因此,保存」線程局部變量」的map並不是是ThreadLocal的成員變量, 而是java.lang.Thread的成員變量。也就是說,線程結束的時候,該map的資源也同時被回收。經過以下代碼:

ThreadLocal的set,get方法中均經過以下方式獲取Map:
ThreadLocalMap map = getMap(t);

而getMap方法的代碼以下:
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

可見:ThreadLocalMap實例是做爲java.lang.Thread的成員變量存儲的,每一個線程有惟一的一個threadLocalMap。這個map以ThreadLocal對象爲key,」線程局部變量」爲值,因此一個線程下能夠保存多個」線程局部變量」。對ThreadLocal的操做,實際委託給當前Thread,每一個Thread都會有本身獨立的ThreadLocalMap實例,存儲的倉庫是Entry[] table;Entry的key爲ThreadLocal,value爲存儲內容;所以在併發環境下,對ThreadLocal的set或get,不會有任何問題。如下爲」線程局部變量」的存儲圖: 在此輸入圖片描述

因爲treadLocalMap是java.util.Thread的成員變量,threadLocal做爲threadLocalMap中的key值,在一個線程中只能保存一個」線程局部變量」。將ThreadLocalMap做爲Thread類的成員變量的好處是: a. 當線程死亡時,threadLocalMap被回收的同時,保存的」線程局部變量」若是不存在其它引用也能夠同時被回收。 b. 同一個線程下,能夠有多個treadLocal實例,保存多個」線程局部變量」。 c. 同一個threadLocal實例,能夠有多個線程使用,保存多個線程的「線程局部變量」。

##ThreadLocal的應用## 咱們在多線程的開發中,常常會考慮到的策略是對一些須要公開訪問的屬性經過設置同步的方式來訪問。這樣每次能保證只有一個線程訪問它,不會有衝突。可是這樣作的結果會使得性能和對高併發的支持不夠。在某些狀況下,若是咱們不必定非要對一個變量共享不可,而是給每一個線程一個這樣的資源副本,讓他們能夠獨立都各自跑各自的,這樣不是能夠大幅度的提升並行度和性能了嗎?

還有的狀況是有的數據自己不是線程安全的,或者說它只能被一個線程使用,不能被其餘線程同時使用。若是等一個線程使用完了再給另一個線程使用就根本不現實。這樣的狀況下,咱們也能夠考慮用ThreadLocal。一個典型的狀況就是咱們鏈接數據庫的時候一般會用到鏈接池。而對數據庫的鏈接不能有多個線程共享訪問。這個時候就須要使用ThreadLocal了。在比較熟悉的兩個框架中,Struts2和Hibernate均有采用ThreadLocal變量,並且對整個框架來講是很是核心的一部分。

Struts2和Struts1的一個重要升級就是對request,response兩個對象的解耦,Struts2的Action方法中再也不須要傳遞request,response參數。可是Struts2不經過方法直接傳入request,response對象,那麼這兩個值是如何傳遞的呢?

Struts2採用的正是ThreadLocal變量。在每次接收到請求時,Struts2在調用攔截器和action前,經過將request,response對象放入ActionContext實例中,而ActionContext實例是做爲」線程局部變量」存入ThreadLocal actionContext中。

public class ActionContext implements Serializable {
    static ThreadLocal actionContext = new ThreadLocal();
. . .

因爲actionContext是」線程局部變量」,這樣咱們經過ServletActionContext.getRequest()便可得到本線程的request對象,並且在本地線程的任意類中,都可經過該方法獲取」線程局部變量」,而無需值傳遞,這樣Action類既能夠成爲一個simple類,無需繼承struts2的任意父類。

在利用Hibernate開發DAO模塊時,咱們和Session打的交道最多,因此如何合理的管理Session,避免Session的頻繁建立和銷燬,對於提升系統的性能來講是很是重要的。通常經常使用的Hibernate工廠類,都會經過ThreadLocal來保存線程的session,這樣咱們在同一個線程中的處理,工廠類的getSession()方法,便可以屢次獲取同一個Session進行操做,closeSession方法可在不傳入參數的狀況下,正確關閉session。

Hiberante的Session 工具類HibernateUtil,這個類是Hibernate官方文檔中HibernateUtil類,用於session管理。以下:

public class HibernateUtil {
    private static Log log = LogFactory.getLog(HibernateUtil.class);
    private static final SessionFactory sessionFactory;     //定義SessionFactory
 
    static {
        try {
            // 經過默認配置文件hibernate.cfg.xml建立SessionFactory
            sessionFactory = new Configuration().configure().buildSessionFactory();
        } catch (Throwable ex) {
            log.error("初始化SessionFactory失敗!", ex);
            throw new ExceptionInInitializerError(ex);
        }
    }

    //建立線程局部變量session,用來保存Hibernate的Session
    public static final ThreadLocal session = new ThreadLocal();
 
    /**
     * 獲取當前線程中的Session
     * @return Session
     * @throws HibernateException
     */
    public static Session currentSession() throws HibernateException {
        Session s = (Session) session.get();
        // 若是Session尚未打開,則新開一個Session
        if (s == null) {
            s = sessionFactory.openSession();
            session.set(s);         //將新開的Session保存到線程局部變量中
        }
        return s;
    }
 
    public static void closeSession() throws HibernateException {
        //獲取線程局部變量,並強制轉換爲Session類型
        Session s = (Session) session.get();
        session.set(null);
        if (s != null)
            s.close();
    }
}

在這個類中,因爲沒有重寫ThreadLocal的initialValue()方法,則首次建立線程局部變量session其初始值爲null,第一次調用currentSession()的時候,線程局部變量的get()方法也爲null。所以,對session作了判斷,若是爲null,則新開一個Session,並保存到線程局部變量session中,這一步很是的關鍵,這也是「public static final ThreadLocal session = new ThreadLocal()」所建立對象session能強制轉換爲Hibernate Session對象的緣由。

能夠看到在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中,這可夠麻煩的。或者能夠本身定義一個靜態的map,將當前thread做爲key,建立的session做爲值,put到map中,應該也行,這也是通常人的想法,但事實上,ThreadLocal的實現恰好相反,它是在每一個線程中有一個map,而將ThreadLocal實例做爲key,這樣每一個map中的項數不多,並且當線程銷燬時相應的東西也一塊兒銷燬了,不知道除了這些還有什麼其餘的好處。 ###典型使用方式###

// 摘自 j.u.c.ThreadLocalRandom
private static final ThreadLocal<ThreadLocalRandom> localRandom =  // ThreadLocal對象都是static的,全局共享
    new ThreadLocal<ThreadLocalRandom>() {      // 初始值
        protected ThreadLocalRandom initialValue() {
            return new ThreadLocalRandom();
        }
};

localRandom.get();      // 拿當前線程對應的對象
localRandom.put(...);   // put

##ThreadLocal產生的問題## 在WEB服務器環境下,因爲Tomcat,weblogic等服務器有一個線程池的概念,即接收到一個請求後,直接從線程池中取得線程處理請求;請求響應完成後,這個線程自己是不會結束,而是進入線程池,這樣能夠減小建立線程、啓動線程的系統開銷。

因爲Tomcat線程池的緣由,我最初使用的」線程局部變量」保存的值,在下一次請求依然存在(同一個線程處理),這樣每次請求都是在本線程中取值而不是去memCache中取值,若是memCache中的數據發生變化,也沒法及時更新。

解決方案:處理完成後主動調用該業務treadLocal的remove()方法,將」線程局部變量」清空,避免本線程下次處理的時候依然存在舊數據。

**Sturts2是如何解決線程池的問題呢?**因爲web服務器的線程是屢次使用的,很顯然Struts2在響應完成後,會主動的清除「線程局部變量」中的ActionContext值,在struts2的org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter類中,有這樣的代碼片斷:

finally {
    prepare.cleanupRequest(request);
}

而cleanupRequest方法中有以下代碼:

public void cleanupRequest(HttpServletRequest request) {
    ……//省略部分代碼
    ActionContext.setContext(null);
    Dispatcher.setInstance(null);
}

因而可知,Sturts2在處理完成後,會主動清空」線程局部變量」ActionContext,來達到釋放系統資源的目的。

##ThreadLocal總結## ThreadLocal使用場合主要解決多線程中數據數據因併發產生不一致問題。ThreadLocal爲每一個線程的中併發訪問的數據提供一個副本,經過訪問副原本運行業務,這樣的結果是耗費了內存,單大大減小了線程同步所帶來性能消耗,也減小了線程併發控制的複雜度。

ThreadLocal不能使用原子類型,只能使用Object類型。ThreadLocal的使用比synchronized要簡單得多。

ThreadLocal和Synchonized都用於解決多線程併發訪問。可是ThreadLocal與synchronized有本質的區別。synchronized是利用鎖的機制,使變量或代碼塊在某一時該只能被一個線程訪問。而ThreadLocal爲每個線程都提供了變量的副本,使得每一個線程在某一時間訪問到的並非同一個對象,這樣就隔離了多個線程對數據的數據共享。而Synchronized卻正好相反,它用於在多個線程間通訊時可以得到數據共享。

Synchronized用於線程間的數據共享,而ThreadLocal則用於線程間的數據隔離。

固然ThreadLocal並不能替代synchronized,它們處理不一樣的問題域。Synchronized用於實現同步機制,比ThreadLocal更加複雜。

總之,ThreadLocal不是用來解決對象共享訪問問題的,而主要是提供了保持對象的方法和避免參數傳遞的方便的對象訪問方式。概括了兩點:

  1. 每一個線程中都有一個本身的ThreadLocalMap類對象,能夠將線程本身的對象保持到其中,各管各的,線程能夠正確的訪問到本身的對象。
  2. 將一個共用的ThreadLocal靜態實例做爲key,將不一樣對象的引用保存到不一樣線程的ThreadLocalMap中,而後在線程執行的各處經過這個靜態ThreadLocal實例的get()方法取得本身線程保存的那個對象,避免了將這個對象做爲參數傳遞的麻煩。

##ThreadLocal建議##

  1. ThreadLocal應定義爲靜態成員變量。
  2. 能經過傳值傳遞的參數,不要經過ThreadLocal存儲,以避免形成ThreadLocal的濫用。
  3. 在線程池的狀況下,在ThreadLocal業務週期處理完成時,最好顯式的調用remove()方法,清空」線程局部變量」中的值。
  4. 正常狀況下使用ThreadLocal不會形成內存溢出,弱引用的只是threadLocal,保存的值依然是強引用的,若是threadLocal依然被其餘對象強引用,」線程局部變量」是沒法回收的。
相關文章
相關標籤/搜索