1. 程式人生 > >深入解析ThreadLocal底層實現原理

深入解析ThreadLocal底層實現原理

學習Java中常用的開源框架,Mybatis、Hibernate中設計到執行緒通過資料庫連線物件Connection,對其資料進行操作,都會使用ThreadLocal類來保證Java多執行緒程式訪問和資料庫資料的一致性問題。就想深入瞭解一下ThreadLocal類是怎樣確保執行緒安全的!詳解如下:

一、對其ThreadLocal類的大致瞭解

       ThreadLocal ,也叫執行緒本地變數,可能很多朋友都知道ThreadLocal為變數在每個執行緒中都建立了所使用的的變數副本。使用起來都是線上程的本地工作記憶體中操作,並且提供了set和get方法來訪問拷貝過來的變數副本。底層也是封裝了ThreadLocalMap集合類來綁定當前執行緒和變數副本的關係,各個執行緒獨立並且訪問安全!

  1. publicclass DBUtil {  
  2.     //建立一個儲存資料庫連線物件的ThreadLocal執行緒本地變數
  3.     privatestatic ThreadLocal<Connection> tl = new ThreadLocal<Connection>();  
  4.     static{  
  5.         try {  
  6.             //註冊驅動
  7.             DriverManager.registerDriver(new oracle.jdbc.OracleDriver());  
  8.         } catch (SQLException e) {  
  9.             e.printStackTrace();  
  10.         }  
  11.     }  
  12.     /* 
  13.      * 獲取資料庫的連線物件 
  14.      */
  15.     publicstatic Connection getConnected(){  
  16.         Connection conn = null;  
  17.         conn = tl.get();        //第一步:從ThreadLocal物件當中去獲取
  18.         if(conn == null){       //若沒有獲取到,原始方法獲取
  19.             try {  
  20.                 conn = DriverManager.getConnection("jdbc:oracle:thin:@192.168.122.1:1521/xe"
    ,"store","store_password");  
  21.                 //獲取連線物件以後,都設定為預設手動提交
  22.                 conn.setAutoCommit(false);    
  23.                 //第二部:將連線物件放入對應的ThreadLocal泛型物件tl當中(進而繫結到使用它的執行緒物件上)
  24.                 tl.set(conn);  
  25.             } catch (SQLException e) {  
  26.                 e.printStackTrace();  
  27.             }  
  28.         }  
  29.         return conn;  
  30.     }  
  31.     /* 
  32.      * 關閉資料庫的連線,並刪除對應的ThreadLocal中的物件 
  33.      */
  34.     publicstaticvoid closeConnection(){  
  35.         Connection conn = null;  
  36.         conn = tl.get();        //第三步:使用完畢,再次獲取物件
  37.         if(conn != null){  
  38.             tl.remove();        //第四步:執行緒操作資料庫完畢,移除
  39.             try {  
  40.                 conn.close();  
  41.             } catch (SQLException e) {  
  42.                 e.printStackTrace();  
  43.             }  
  44.         }  
  45.     }  
  46. }  

        上述例子中使用ThreadLocal類來繫結對應執行緒和Connection之間的關係,確保訪問資料庫資料的安全性問題;大家想象一下,如果沒有使用ThreadLocal類來繫結,那麼多個執行緒同時進入getConnected()方法,有可能獲取的是同一個Connection物件,導致執行緒不安全問題!

二、深入理解ThreadLoca類

    (1)set操作,為執行緒繫結變數:    

  1. publicvoid set(T value) {  
  2.    Thread t = Thread.currentThread();//1.首先獲取當前執行緒物件
  3.        ThreadLocalMap map = getMap(t);//2.獲取該執行緒物件的ThreadLocalMap
  4.        if (map != null)  
  5.            map.set(this, value);//如果map不為空,執行set操作,以當前threadLocal物件為key,實際儲存物件為value進行set操作
  6.        else
  7.            createMap(t, value);//如果map為空,則為該執行緒建立ThreadLocalMap
  8.    }  

可以很清楚的看到,ThreadLocal只不過是個入口,真正的變數副本繫結到當前執行緒上的。

  1. //Thread中的成員變數
  2.     ThreadLocal.ThreadLocalMap threadLocals = null;     //每個Thread執行緒中都封裝了一個ThreadLocalMap物件
  3.     //ThreadLocal類中獲取Thread類中的ThreadLocalMap物件
  4.     ThreadLocalMap getMap(Thread t) {  
  5.     return t.threadLocals;  
  6.     }  
  7.     //ThreadLocal類中建立Thread類中的ThreadLocalMap成員物件
  8.     void createMap(Thread t, T firstValue) {  
  9.         t.threadLocals = new ThreadLocalMap(this, firstValue);  
  10.     }  

現在,我們可以看出ThreadLocal的設計思想了:

(1) ThreadLocal僅僅是個變數訪問的入口;

(2) 每一個Thread物件都有一個ThreadLocalMap物件,這個ThreadLocalMap持有物件的引用;

(3) ThreadLocalMap以當前的threadLocal物件為key,以真正的儲存物件為value。get()方法時通過threadLocal例項就可以找到繫結在當前執行緒上的副本物件。

看上去有點繞。我們完全可以設計成Map<Thread,Value>這種形式,一個執行緒對應一個儲存物件。

ThreadLocal這樣設計有兩個目的:

        第一:可以保證當前執行緒結束時,相關物件可以立即被回收;第二:ThreadLocalMap元素會大大減少,因為Map過大容易造成雜湊衝突而導致效能降低。

(2)看get()方法

  1. public T get() {  
  2.     Thread t = Thread.currentThread();//1.首先獲取當前執行緒
  3.         ThreadLocalMap map = getMap(t);//2.獲取執行緒的map物件
  4.         if (map != null) {//3.如果map不為空,以threadlocal例項為key獲取到對應Entry,然後從Entry中取出物件即可。
  5.             ThreadLocalMap.Entry e = map.getEntry(this);  
  6.             if (e != null)  
  7.                 return (T)e.value;  
  8.         }  
  9.         return setInitialValue();//如果map為空,也就是第一次沒有呼叫set直接get(或者呼叫過set,又呼叫了remove)時,為其設定初始值
  10.     }  
  1. void createMap(Thread t, T firstValue) {    //this指的是ThreadLocal物件
  2.     t.threadLocals = new ThreadLocalMap(this, firstValue);  
  3. }  
  4. private T setInitialValue() {  
  5.     T value = initialValue();  
  6.     Thread t = Thread.currentThread();  
  7.     ThreadLocalMap map = getMap(t);  
  8.     if (map != null)  
  9.          map.set(this, value);  
  10.     else
  11.         createMap(t, value);  
  12.         return value;  
  13. }  

三、應用場景:

        ThreadLocal物件通常用於房子對可變的單例項變數或全域性變數進行共享。例如:由於JDBC的連線物件不是執行緒安全的,因此,當多個執行緒應用程式在沒有協同的情況下,使用全域性變數時,就是執行緒不安全的。通過將JDBC的連線物件儲存到ThreadLocal中,每個執行緒都會擁有自己的連線物件副本。

        ThreadLocal在Spring的事物管理,包括Hibernate管理等都有出現,在web開發中,有事會用來管理使用者回話HttpSession,web互動這種典型的一請求一執行緒的場景似乎比較適合使用ThreadLocal,但是需要注意的是,由於此時session與執行緒關聯,而Tomcat這些web伺服器多采用執行緒池機制,也就是說執行緒是可以複用的,所以在每次進入的時候都需要重新進行set操作,或者使用完畢以後及時remove掉!

  1. publicvoid remove() {  
  2.         ThreadLocalMap m = getMap(Thread.currentThread());        //先獲取ThreadLocalMap物件例項
  3.         if (m != null)        //直接通過threadLocal例項刪除value值
  4.             m.remove(this);  
  5.     }