Java多線程下 ThreadLocal 的應用實例

ThreadLocal很容易讓人望文生義,想固然地認爲是一個「本地線程」 。其實,ThreadLocal並非一個 Thread,而是 Thread 的局部變量,也許把它命名爲 ThreadLocalVariable更容易讓人理解一些。當使用 ThreadLocal 維護變量時,ThreadLocal 爲每一個使用該變量的線程提供獨立的變量副本,因此每個線程均可以獨立地改變本身的副本,而不會影響其它線程所對應的副本。 
 
      首先,ThreadLocal 不是用來解決共享對象的多線程訪問問題的,通常狀況下,經過ThreadLocal.set() 到線程中的對象是該線程本身使用的對象,其餘線程是不須要訪問的,也訪問不到的。各個線程中訪問的是不一樣的對象。 
 
      另外,說ThreadLocal使得各線程可以保持各自獨立的一個對象,並非經過ThreadLocal.set()來實現的,而是經過每一個線程中的new 對象 的操做來建立的對象,每一個線程建立一個,不是什麼對象的拷貝或副本。經過ThreadLocal.set()將這個新建立的對象的引用保存到各線程的本身的一個map中,每一個線程都有這樣一個map,執行ThreadLocal.get()時,各線程從本身的map中取出放進去的對象,所以取出來的是各自本身線程中的對象,ThreadLocal實例是做爲map的key來使用的。 
 
      若是ThreadLocal.set()進去的東西原本就是多個線程共享的同一個對象,那麼多個線程的ThreadLocal.get()取得的仍是這個共享對象自己,仍是有併發訪問問題。 

JDK 5 之後提供了泛型支持,ThreadLocal 被定義爲支持泛型: 
public class ThreadLocal<T> extends Object 
T 爲線程局部變量的類型。該類定義了 4 個方法: 
     1) protected T initialValue(): 返回此線程局部變量的當前線程的「初始值」。線程第一次使用 get()  方法訪問變量時將調用此方法,但若是線程以前調用了 set(T)  方法,則不會對該線程再調用 initialValue  方法。一般,此方法對每一個線程最多調用一次,但若是在調用 get() 後又調用了 remove(),則可能再次調用此方法。  該實現返回 null;若是程序員但願線程局部變量具備 null  之外的值,則必須爲 ThreadLocal 建立子類,並重寫此方法。一般將使用匿名內部類完成此操做。 
     2)public T get():返回此線程局部變量的當前線程副本中的值。若是變量沒有用於當前線程的值,則先將其初始化爲調用 initialValue() 方法返回的值。 
     3)public void set(T value):將此線程局部變量的當前線程副本中的值設置爲指定值。大部分子類不須要重寫此方法,它們只依靠 initialValue()  方法來設置線程局部變量的值。 

     4)public void remove():移除此線程局部變量當前線程的值。若是此線程局部變量隨後被當前線程讀取,且這期間當前線程沒有設置其值,則將調用其 initialValue()  方法從新初始化其值。這將致使在當前線程屢次調用 initialValue  方法。  html

下面是一個使用 ThreadLocal 的例子,每一個線程產生本身獨立的序列號。就是使用ThreadLocal存儲每一個線程獨立的序列號複本,線程之間互不干擾。 
package sync;  
public class SequenceNumber {  
  // 定義匿名子類建立ThreadLocal的變量  
  private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>() {  
   // 覆蓋初始化方法  
   public Integer initialValue() {  
      return 0;  
  }  
 };  
  // 下一個序列號  
  public int getNextNum() {  
   seqNum.set(seqNum.get() + 1);  
   return seqNum.get();  
 }  
  private static class TestClient extends Thread {  
   private SequenceNumber sn;  
   public TestClient(SequenceNumber sn) {  
    this.sn = sn;  
  }  
   // 線程產生序列號  
   public void run() {  
    for (int i = 0; i < 3; i++) {  
    System.out.println("thread[" + Thread.currentThread().getName()   + "] sn[" + sn.getNextNum() + "]");  
   }  
  }  
 }  
  /** 
  * @param args 
  */  
  public static void main(String[] args) {  
  SequenceNumber sn = new SequenceNumber();  
      // 三個線程產生各自的序列號  
     TestClient t1 = new TestClient(sn);  
     TestClient t2 = new TestClient(sn);  
     TestClient t3 = new TestClient(sn);  
     t1.start();  
     t2.start();  
     t3.start();  
 }  
}
程序的運行結果以下:
thread[Thread-1] sn[1]  
thread[Thread-1] sn[2]  
thread[Thread-1] sn[3]  
thread[Thread-2] sn[1]  
thread[Thread-2] sn[2]  
thread[Thread-2] sn[3]  
thread[Thread-0] sn[1]
thread[Thread-0] sn[2]  
thread[Thread-0] sn[3]


從運行結果能夠看出,使用了 ThreadLocal 後,每一個線程產生了獨立的序列號,沒有相互干擾。一般咱們經過匿名內部類的方式定義 ThreadLocal的子類,提供初始的變量值。 

        ThreadLocal和線程同步機制相比有什麼優點呢?ThreadLocal和線程同步機制都是爲了解決多線程中相同變量的訪問衝突問題。 
 
        在同步機制中,經過對象的鎖機制保證同一時間只有一個線程訪問變量。這時該變量是多個線程共享的,使用同步機制要求程序慎密地分析何時對變量進行讀寫,何時須要鎖定某個對象,何時釋放對象鎖等繁雜的問題,程序設計和編寫難度相對較大。 而 ThreadLocal 則從另外一個角度來解決多線程的併發訪問。ThreadLocal 會爲每個線程提供一個獨立的變量副本,從而隔離了多個線程對數據的訪問衝突。由於每個線程都擁有本身的變量副本,從而也就沒有必要對該變量進行同步了。ThreadLocal 提供了線程安全的共享對象,在編寫多線程代碼時,能夠把不安全的變量封裝進 ThreadLocal。 
 
        歸納起來講,對於多線程資源共享的問題,同步機制採用了「以時間換空間」的方式,而 ThreadLocal 採用了「以空間換時間」的方式。前者僅提供一份變量,讓不一樣的線程排隊訪問,然後者爲每個線程都提供了一份變量,所以能夠同時訪問而互不影響。 
 
        須要注意的是 ThreadLocal 對象是一個本質上存在風險的工具,應該在徹底理解將要使用的線程模型以後,再去使用 ThreadLocal 對象。這就引出了線程池(thread pooling)的問題,線程池是一種線程重用技術,有了線程池就沒必要爲每一個任務建立新的線程,一個線程可能會屢次使用,用於這種環境的任何 ThreadLocal 對象包含的都是最後使用該線程的代碼所設置的狀態,而不是在開始執行新線程時所具備的未被初始化的狀態。 那麼 ThreadLocal 是如何實現爲每一個線程保存獨立的變量的副本的呢?經過查看它的源代碼,咱們會發現,是經過把當前「線程對象」看成鍵,變量做爲值存儲在一個 Map 中。  java

private T setInitialValue() {  
        T value = initialValue();  
        Thread t = Thread.currentThread();  
        ThreadLocalMap map = getMap(t);  
        if (map != null)  
            map.set(this, value);  
        else  
            createMap(t, value);  
        return value;  
}

ThreadLocal不是用來解決對象共享訪問問題的,而主要是提供了保持對象的方法和避免參數傳遞的方便的對象訪問方式。概括了兩點: 
         一、每一個線程中都有一個本身的ThreadLocalMap類對象,能夠將線程本身的對象保持到其中,各管各的,線程能夠正確的訪問到本身的對象。 
         二、將一個共用的ThreadLocal靜態實例做爲key,將不一樣對象的引用保存到不一樣線程的ThreadLocalMap中,而後在線程執行的各處經過這個靜ThreadLocal實例的get()方法取得本身線程保存的那個對象,避免了將這個對象做爲參數傳遞的麻煩。  程序員


Synchronized仍是ThreadLocal?  web

   ThreadLocal以空間換取時間,提供了一種很是簡便的多線程實現方式。由於多個線程併發訪問無需進行等待,因此使用ThreadLocal 會得到更大的性能。雖然使用ThreadLocal會帶來更多的內存開銷,但這點開銷是微不足道的。由於保存在ThreadLocal中的對象,一般都是比較小的對象。另外使用ThreadLocal不能使用原子類型,只能使用Object類型。ThreadLocal的使用比synchronized要簡單得多。

   ThreadLocal和Synchonized都用於解決多線程併發訪問。可是ThreadLocal與synchronized有本質的區別。synchronized是利用鎖的機制,使變量或代碼塊在某一時該只能被一個線程訪問。而ThreadLocal爲每個線程都提供了變量的副本,使得每一個線程在某一時間訪問到的並非同一個對象,這樣就隔離了多個線程對數據的數據共享。而Synchronized卻正好相反,它用於在多個線程間通訊時可以得到數據共享。

Synchronized用於線程間的數據共享,而ThreadLocal則用於線程間的數據隔離。

固然ThreadLocal並不能替代synchronized,它們處理不一樣的問題域。Synchronized用於實現同步機制,比ThreadLocal更加複雜。 安全

參考: 多線程

http://my.oschina.net/huangyong/blog/159725
http://my.oschina.net/huangyong/blog/159489
http://www.itokit.com/2012/0817/74676.html
http://www.java3z.com/cwbwebhome/article/article20/200026.html?id=4841 併發

相關文章
相關標籤/搜索