正確理解ThreadLocal

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

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

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

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

Java代碼 複製代碼收藏代碼
  1. privatestaticfinal ThreadLocal threadSession = new ThreadLocal();  


  2. publicstatic Session getSession() throws InfrastructureException {  

  3.    Session s = (Session) threadSession.get();  

  4. try {  

  5. if (s == null) {  

  6.            s = getSessionFactory().openSession();  

  7.            threadSession.set(s);  

  8.        }  

  9.    } catch (HibernateException ex) {  

  10. thrownew InfrastructureException(ex);  

  11.    }  

  12. return s;  

  13. }  


可以看到在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中的項數很少,而且當線程銷燬時相應的東西也一起銷燬了,不知道除了這些還有什麼其他的好處。

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


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

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

下面來看看ThreadLocal的實現原理(jdk1.5源碼)
Java代碼 複製代碼收藏代碼
  1. publicclass ThreadLocal<T> {  

  2. /**

  3.     * ThreadLocals rely on per-thread hash maps attached to each thread

  4.     * (Thread.threadLocals and inheritableThreadLocals).  The ThreadLocal

  5.     * objects act as keys, searched via threadLocalHashCode.  This is a

  6.     * custom hash code (useful only within ThreadLocalMaps) that eliminates

  7.     * collisions in the common case where consecutively constructed

  8.     * ThreadLocals are used by the same threads, while remaining well-behaved

  9.     * in less common cases.

  10.     */

  11. privatefinalint threadLocalHashCode = nextHashCode();  


  12. /**

  13.     * The next hash code to be given out. Accessed only by like-named method.

  14.     */

  15. privatestaticint nextHashCode = 0;  


  16. /**

  17.     * The difference between successively generated hash codes - turns

  18.     * implicit sequential thread-local IDs into near-optimally spread

  19.     * multiplicative hash values for power-of-two-sized tables.

  20.     */

  21. privatestaticfinalint HASH_INCREMENT = 0x61c88647;  


  22. /**

  23.     * Compute the next hash code. The static synchronization used here

  24.     * should not be a performance bottleneck. When ThreadLocals are

  25.     * generated in different threads at a fast enough rate to regularly

  26.     * contend on this lock, memory contention is by far a more serious

  27.     * problem than lock contention.

  28.     */

  29. privatestaticsynchronizedint nextHashCode() {  

  30. int h = nextHashCode;  

  31.        nextHashCode = h + HASH_INCREMENT;  

  32. return h;  

  33.    }  


  34. /**

  35.     * Creates a thread local variable.

  36.     */

  37. public ThreadLocal() {  

  38.    }  


  39. /**

  40.     * Returns the value in the current thread's copy of this thread-local

  41.     * variable.  Creates and initializes the copy if this is the first time

  42.     * the thread has called this method.

  43.     *

  44.     * @return the current thread's value of this thread-local

  45.     */

  46. public T get() {  

  47.        Thread t = Thread.currentThread();  

  48.        ThreadLocalMap map = getMap(t);  

  49. if (map != null)  

  50. return (T)map.get(this);  


  51. // Maps are constructed lazily.  if the map for this thread

  52. // doesn't exist, create it, with this ThreadLocal and its

  53. // initial value as its only entry.

  54.        T value = initialValue();  

  55.        createMap(t, value);  

  56. return value;  

  57.    }  


  58. /**

  59.     * Sets the current thread's copy of this thread-local variable

  60.     * to the specified value.  Many applications will have no need for

  61.     * this functionality, relying solely on the {@link #initialValue}

  62.     * method to set the values of thread-locals.

  63.     *

  64.     * @param value the value to be stored in the current threads' copy of

  65.     *        this thread-local.

  66.     */

  67. publicvoid set(T value) {  

  68.        Thread t = Thread.currentThread();  

  69.        ThreadLocalMap map = getMap(t);  

  70. if (map != null)  

  71.            map.set(this, value);  

  72. else

  73.            createMap(t, value);  

  74.    }  


  75. /**

  76.     * Get the map associated with a ThreadLocal. Overridden in

  77.     * InheritableThreadLocal.

  78.     *

  79.     * @param  t the current thread

  80.     * @return the map

  81.     */

  82.    ThreadLocalMap getMap(Thread t) {  

  83. return t.threadLocals;  

  84.    }  


  85. /**

  86.     * Create the map associated with a ThreadLocal. Overridden in

  87.     * InheritableThreadLocal.

  88.     *

  89.     * @param t the current thread

  90.     * @param firstValue value for the initial entry of the map

  91.     * @param map the map to store.

  92.     */

  93. void createMap(Thread t, T firstValue) {  

  94.        t.threadLocals = new ThreadLocalMap(this, firstValue);  

  95.    }  


  96.    .......  


  97. /**

  98.     * ThreadLocalMap is a customized hash map suitable only for

  99.     * maintaining thread local values. No operations are exported

  100.     * outside of the ThreadLocal class. The class is package private to

  101.     * allow declaration of fields in class Thread.  To help deal with

  102.     * very large and long-lived usages, the hash table entries use

  103.     * WeakReferences for keys. However, since reference queues are not

  104.     * used, stale entries are guaranteed to be removed only when

  105.     * the table starts running out of space.

  106.     */

  107. staticclass ThreadLocalMap {  


  108.    ........  


  109.    }  


  110. }  



可以看到ThreadLocal類中的變量只有這3個int型:
Java代碼 複製代碼收藏代碼
  1. privatefinalint threadLocalHashCode = nextHashCode();  

  2. privatestaticint nextHashCode = 0;  

  3. privatestaticfinalint HASH_INCREMENT = 0x61c88647;  


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

可以來看一下創建一個ThreadLocal實例即new ThreadLocal()時做了哪些操作,從上面看到構造函數ThreadLocal()裏什麼操作都沒有,唯一的操作是這句:
Java代碼 複製代碼收藏代碼
  1. privatefinalint threadLocalHashCode = nextHashCode();  


那麼nextHashCode()做了什麼呢:
Java代碼 複製代碼收藏代碼
  1. privatestaticsynchronizedint nextHashCode() {  

  2. int h = nextHashCode;  

  3.    nextHashCode = h + HASH_INCREMENT;  

  4. return h;  

  5. }  

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

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

看一下上面的set()方法,兩句合併一下成爲
Java代碼 複製代碼收藏代碼
  1. ThreadLocalMap map = Thread.currentThread().threadLocals;  


這個ThreadLocalMap 類是ThreadLocal中定義的內部類,但是它的實例卻用在Thread類中:
Java代碼 複製代碼收藏代碼
  1. publicclass Thread implements Runnable {  

  2.    ......  


  3. /* ThreadLocal values pertaining to this thread. This map is maintained

  4.     * by the ThreadLocal class. */

  5.    ThreadLocal.ThreadLocalMap threadLocals = null;    

  6.    ......  

  7. }  



再看這句:
Java代碼 複製代碼收藏代碼
  1. if (map != null)  

  2.    map.set(this, value);  


也就是將該ThreadLocal實例作爲key,要保持的對象作爲值,設置到當前線程的ThreadLocalMap 中,get()方法同樣大家看了代碼也就明白了,ThreadLocalMap 類的代碼太多了,我就不帖了,自己去看源碼吧。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章