天天看點

【轉】正确了解 ThreadLocal

關于 ThreadLocal ,源碼讀起來有些費勁 ------ 如果你對它的原理事先沒有一個大概的了解的話。網上有很多文章模棱兩可,甚至有不少錯誤。有幸看到這位作者的部落格,搬過來備忘一下。

原文位址: [url=http://www.iteye.com/topic/103804]正确了解 ThreadLocal[/url]

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

[b]另外,說 ThreadLocal 使得各線程能夠保持各自獨立的一個對象,并不是通過 ThreadLocal.set() 來實作的,而是通過每個線程中的“new 對象”的操作來建立的對象,每個線程建立一個,不是什麼對象的拷貝或副本。[/b]通過 ThreadLocal.set() 将這個新建立的對象的引用儲存到各線程的自己的一個 map 中,每個線程都有這樣一個 map ,執行 ThreadLocal.get() 時,各線程從自己的 map 中取出放進去的對象,是以取出來的是各自自己線程中的對象, ThreadLocal 執行個體是作為 map 的 key 來使用的。

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

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

可以看到在 getSession() 方法中,首先判斷目前線程中有沒有放進去 session ,如果還沒有,那麼通過 getSessionFactory().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 中的項數很少,而且當線程銷毀時相應的東西也一起銷毀了,不知道除了這些還有什麼其他的好處。

[color=red]總之, ThreadLocal 不是用來解決對象共享通路問題的,而主要是提供了保持對象的方法和避免參數傳遞的友善的對象通路方式。歸納了兩點:

1. 每個線程中都有一個自己的 ThreadLocalMap 類對象,可以将線程自己的對象保持到其中,各管各的,線程可以正确的通路到自己的對象。

2. 将一個共用的 ThreadLocal 靜态執行個體作為 key ,将不同對象的引用儲存到不同線程的 ThreadLocalMap 中,然後線上程執行的各處通過這個靜态 ThreadLocal 執行個體的 get() 方法取得自己線程儲存的那個對象,避免了将這個對象作為參數傳遞的麻煩。[/color]

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

[color=red]ThreadLocal 的應用場合,我覺得最适合的是按線程多執行個體(每個線程對應一個執行個體)的對象的通路,并且這個對象很多地方都要用到。[/color]

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

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

而作為 ThreadLocal 執行個體的變量隻有 threadLocalHashCode 這一個, nextHashCode 和 HASH_INCREMENT 是 ThreadLocal 類的靜态變量,實際上 HASH_INCREMENT 是一個常量,表示了連續配置設定的兩個 ThreadLocal 執行個體的 threadLocalHashCode 值的增量,而 nextHashCode 則表示了即将配置設定的下一個 ThreadLocal 執行個體的 threadLocalHashCode 的值。

可以來看一下建立一個 ThreadLocal 執行個體即 new ThreadLocal() 時做了哪些操作,從上面看到構造函數ThreadLocal() 裡什麼操作都沒有,唯一的操作是這句:

那麼 nextHashCode() 做了什麼呢:

就是将 ThreadLocal 類的下一個 hashCode 值即 nextHashCode 的值賦給執行個體的threadLocalHashCode ,然後 nextHashCode 的值增加 HASH_INCREMENT 這個值。

是以 ThreadLocal 執行個體的變量隻有這個 threadLocalHashCode ,而且是 final 的,用來區分不同的 ThreadLocal 執行個體。 ThreadLocal 類主要是作為工具類來使用,那麼 ThreadLocal.set() 進去的對象是放在哪兒的呢?

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

這個 ThreadLocalMap 類是 ThreadLocal 中定義的内部類,但是它的執行個體卻用在 Thread 類中:

再看這句:

也就是将該 ThreadLocal 執行個體作為 key ,要保持的對象作為值,設定到目前線程的 ThreadLocalMap 中。 get()方法同樣大家看了代碼也就明白了, ThreadLocalMap 類的代碼太多了,我就不帖了,自己去看源碼吧。

最後轉載這篇文章的“作者”我再羅嗦一句: ThreadLocal 的關鍵是:以線程共享的 ThreadLocal 執行個體本身作為 key ,把 set 進去的值放在目前線程的 ThreadLocalMap 中,進而實作線程隔離。

繼續閱讀