深入理解java:2.4. 線程本地變量 java.lang.ThreadLocal類


ThreadLocal,很多人都叫它做線程本地變量,也有些地方叫做線程本地存儲,其實意思差不多。

可能很多朋友都知道ThreadLocal為變量在每個線程中都創建了一個副本,那樣每個線程可以訪問自己內部的副本變量。

這句話從表面上看起來理解正確,但實際上這種理解是不太正確的。下面我們細細道來。

 

多線程並發執行時,需要數據共享,因此才有了volatile變量解決 多線程間的數據可見性,

也有了鎖的同步機制,使變量或代碼塊在某一時該,只能被一個線程訪問,確保共享數據的正確性。(Synchronized用於線程間的數據共享的)

多線程並發執行時,並不是所有數據都需要共享的,這些不需要共享的數據,讓每個線程去維護就OK了,ThreadLocal就是用於線程間的數據隔離的。

 

深入解析ThreadLocal類:

先我們來看一下ThreadLocal類是如何為每個線程創建一個變量的副本的。

  先看下get方法的實現:

  

第一句是取得當前線程,然后通過getMap(t)方法獲取到一個map,map的類型為ThreadLocalMap。

然后接着下面獲取到Entry鍵值對,注意這里獲取Entry時參數傳進去的是  this,即ThreadLocal實例,而不是當前線程t。如果獲取成功,則返回value值。 

如果map為空,則調用setInitialValue方法返回value。

 

接着看一下getMap方法中做了什么:

  

在getMap中,是調用當期線程t,返回當前線程t中的一個成員變量threadLocals,類型為ThreadLocalMap。

這里意味着每一個線程都自帶一個ThreadLocalMap成員變量。

 

繼續取看ThreadLocalMap的實現:

 

可以看到ThreadLocalMap的Entry繼承了WeakReference,並且使用ThreadLocal作為鍵值

也就是說WeakReference封裝了ThreadLocal,並作為了ThreadLocalMap的Entry的Key。

 

總結一下,在每個線程Thread內部有一個ThreadLocalMap類型的成員變量threadLocals,

這個ThreadLocalMap成員變量的Entry的Key,當前ThreadLocal變量的WeakReference封裝,value為變量。

 

為何ThreadLocalMap的鍵值為ThreadLocal對象? 因為每個線程中可能需要有多個threadLocal變量,也就是ThreadLocalMap里面可能會有多個Entry。

在每個線程內部 第一次調用ThreadLocal.get方法時,都會返回Null。因為默認情況下,initialValue方法返回的是null。

null 賦給(強轉) 基本數據類型時會拋的空指針,null賦給 引用類型沒問題。

 

可以在ThreadLocal的構造函數重寫initialValue()方法。如下

ThreadLocal<Long> longLocal = new ThreadLocal<Long>(){

         protected Long initialValue() {
             return Thread.currentThread().getId();
         };
     };

或者在調用ThreadLocal.get方法之前,需要先執行set(),以保證threadlocals中有值。

或者value為引用類型變量null賦給 引用類型沒問題。,如下,hibernate中典型的ThreadLocal的應用:

Java代碼 
  1. private static final ThreadLocal threadSession = new ThreadLocal();  
  2.   
  3. public static Session getSession() throws InfrastructureException {  
  4.     Session s = (Session) threadSession.get();  
  5.     try {  
  6.         if (s == null) {  
  7.             s = getSessionFactory().openSession();  //分發一個實例 的內存地址,一般就是new一個實例了
  8.             threadSession.set(s);  
  9.         }  
  10.     } catch (HibernateException ex) {  
  11.         throw new InfrastructureException(ex);  
  12.     }  
  13.     return s;  
  14. }  

開篇說ThreadLocal創建副本 的說法是不太正確的。為什么?

從上面這個hibernate的例子來看,這是一個使用ThreadLocal解決數據庫連接的單例 在多線程中同時操作查詢和關閉的情況。

首先這里面不是創建副本,而是分發新的內存地址(即,新的數據庫連接的單例的內存地址,以當前ThreadLocal為key,value指向傳入新的數據庫連接的單例的內存地址。

從而達到單個線程獲取數據連接的線程安全而已,也就是每個線程都有一個獨立的數據庫連接的單例

假設相反情況,一個數據庫連接單例 如果在2個線程中被同時引用,2線程分別同一時間操作讀取和close,肯定會出現沖突。

所以需要減少每次new的開銷還是得使用數據庫連接

 

ThreadLocal的內存泄露問題:

當使用線程池來復用線程時,一個線程使用完后並不會銷毀線程,那么 分發的那個實例會一直綁定在這個線程上。

由於WeakReference封裝了ThreadLocal,並作為了ThreadLocalMap的Entry的Key。如果在某些時候ThreadLocal對象被賦Null的話,弱引用會被GC收集,這樣就會導致Entry的Value對象找不到,

線程被復用后如果有調用ThreadLocal.get/set方法的話,方法里面會去做遍歷清除  以[ThreadLocal=Null ]為Key的Entry; 但如果一直沒調用ThreadLocal.get/set方法的話就會導致內存泄漏了。

 

所以一般線程用完ThreadLocal后,要調用threadLocal.remove(); 如下

  1. public static void close() {  
  2.         // 獲取當前線程內共享的Connection  
  3.         Connection conn = threadLocal.get();  
  4.         try {  
  5.             // 判斷是否已經關閉  
  6.             if(conn != null && !conn.isClosed()) {  
  7.                 // 關閉資源  
  8.                 conn.close();  
  9.                 // 移除Connection  
  10.                 threadLocal.remove();  
  11.                 conn = null;  
  12.             }  
  13.         } catch (SQLException e) {  
  14.             // 異常處理  
  15.         }  
  16.     } 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM