ThreadLocal的設計與使用(原理篇)

java牛發表於2015-08-01

在jdk1.2推出時開始支援java.lang.ThreadLocal。在J2SE5.0中的宣告為:

 
         public class ThreadLocal<T> extends Object
 
      ThreadLocal是什麼呢?其實ThreadLocal並非是一個執行緒的本地實現版本,它並不是一個Thread,而是thread local variable(執行緒區域性變數)。也許把它命名為ThreadLocalVar更加合適。執行緒區域性變數(ThreadLocal)其實的功用非常簡單,就是為每一個使用該變數的執行緒都提供一個變數值的副本,是每一個執行緒都可以獨立地改變自己的副本,而不會和其它執行緒的副本衝突。從執行緒的角度看,就好像每一個執行緒都完全擁有該變數。
 
      首先我們看一下ThreadLocal類的介面和設計思路。在J2SE5.0中,該類有1個預設建構函式,4個普通函式:
      protected ThreadLocal initialValue(),顯然是為了子類重寫而特意實現的。該方法返回當前執行緒在該執行緒區域性變數的初始值,這個方法是一個延遲呼叫方法,在一個執行緒第1次呼叫get()或者set(Object)時才執行,並且僅執行1次;public ThreadLocal get(),返回當前執行緒的執行緒區域性變數副本;public void set(ThreadLocal value),設定當前執行緒的執行緒區域性變數副本的值;public void remove(),移除當前執行緒的執行緒區域性變數副本的值以釋放儲存空間。
 
      從下面這個參考實現,我們可以看出ThreadLocal的工作原理:

public 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);
  }

  public Object initialValue() {
    return null;
  }
}

 
      JDK中的ThreadLocal的實現總體思路也類似於此,但這並不是一個工業強度的實現。首先,每個 get() 和 set() 操作都需要values 對映表上的同步,而且如果多個執行緒同時訪問同一個ThreadLocal,那麼將發生衝突。此外,這個實現也是不切實際的,因為用Thread 物件做 values 對映表中的key將導致無法線上程退出後對 Thread 進行垃圾回收,而且也無法對死執行緒的 ThreadLocal的特定於執行緒的值進行垃圾回收。從j2sdk5.0的src來看,並非在ThreadLocal中有一個Map,而是在每個Thread中存在這樣一個Map,具體是ThreadLocal.ThreadLocalMap。當用set時候,往當前執行緒裡面的Map裡 put 的key是當前的ThreadLocal物件。而不是把當前Thread作為Key值put到ThreadLocal中的Map裡。
 
      ThreadLocal的使用。如果希望執行緒區域性變數初始化其它值,那麼需要自己實現ThreadLocal的子類並重寫該方法,通常使用一個inner anonymous class對ThreadLocal進行子類化,比如下面的例子,SerialNum類為每一個類分配一個序號:

 public class SerialNum {
     // The next serial number to be assigned
     private static int nextSerialNum = 0;

     private static ThreadLocal serialNum = new ThreadLocal() {
         protected synchronized Object initialValue() {
             return new Integer(nextSerialNum++);
         }
     };

     public static int get() {
         return ((Integer) (serialNum.get())).intValue();
     }
}

      線上程是活動的並且ThreadLocal物件是可訪問的時,該執行緒就持有一個到該執行緒區域性變數副本的隱含引用,當該執行緒執行結束後,該執行緒擁有的所有執行緒區域性變數的副本都將失效,並等待垃圾收集器收集。
  由於ThreadLocal中可以持有任何型別的物件,所以使用ThreadLocal獲取當前執行緒的值是需要進行強制型別轉換。但隨著J2SE5.0將模版引入,新的支援模版引數的ThreadLocal<T>類將從中受益。也可以減少強制型別轉換,並將一些錯誤檢查提前到了編譯期,將一定程度地簡化ThreadLocal的使用。
 
  ThreadLocal和其它同步機制相比有什麼優勢呢?ThreadLocal和其它所有的同步機制都是為了解決多執行緒中的對同一變數的訪問衝突,在普通的同步機制中,是通過物件加鎖來實現多個線 程對同一變數的安全訪問的。這時該變數是多個執行緒共享的,使用這種同步機制需要很細緻地分析在什麼時候對變數進行讀寫,什麼時候需要鎖定某個物件,什麼時候釋放該物件的鎖等等很多。所有這些都是因為多個執行緒共享了資源造成的。ThreadLocal就從另一個角度來解決多執行緒的併發訪問,ThreadLocal會為每一個執行緒維護一個和該執行緒繫結的變數的副本,從而隔離了多個執行緒的資料,每一個執行緒都擁有自己的變數副本,從而也就沒有必要對該變數進行同步了。ThreadLocal提供了執行緒安全的共享物件,在編寫多執行緒程式碼時,可以把不安全的整個變數封裝進ThreadLocal,或者把該物件的特定於執行緒的狀態封裝進ThreadLocal
  當然ThreadLocal並不能替代同步機制,兩者面向的問題領域不同。同步機制是為了同步多個執行緒對相同資源的併發訪問,是為了多個執行緒之間進行通訊的有效方式;而ThreadLocal是隔離多個執行緒的資料共享,從根本上就不在多個執行緒之間共享資源(變數),這樣當然不需要對多個執行緒進行同步了。所以,如果你需要進行多個執行緒之間進行通訊,則使用同步機制;如果需要隔離多個執行緒之間的共享衝突,可以使用ThreadLocal,這將極大地簡化我們的程式,使程式更加易讀、簡潔。

若轉載請註明出處!若有疑問,請回復交流!


相關文章