1. 程式人生 > >Java ThreadLocal解決執行緒安全問題

Java ThreadLocal解決執行緒安全問題

轉載自:http://jiaozhiguang-126-com.iteye.com/blog/1667110

ThreadLocal是什麼 


早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal為解決多執行緒程式的併發問題提供了一種新的思路。使用這個工具類可以很簡潔地編寫出優美的多執行緒程式。 
ThreadLocal,顧名思義,它不是一個執行緒,而是執行緒的一個本地化物件。當工作於多執行緒中的物件使用ThreadLocal維護變數時,ThreadLocal為每個使用該變數的執行緒分配一個獨立的變數副本。所以每一個執行緒都可以獨立地改變自己的副本,而不會影響其他執行緒所對應的副本。從執行緒的角度看,這個變數就像是執行緒的本地變數,這也是類名中“Local”所要表達的意思。 



ThreadLocal的介面方法 

ThreadLocal類介面很簡單,只有4個方法,我們先來了解一下。 
void set(Object value) 
   設定當前執行緒的執行緒區域性變數的值; 
public Object get() 
   該方法返回當前執行緒所對應的執行緒區域性變數; 
public void remove() 
   將當前執行緒區域性變數的值刪除,目的是為了減少記憶體的佔用,該方法是JDK 5.0新增的方法。需要指出的是,當執行緒結束後,對應該執行緒的區域性變數將自動被垃圾回收,所以顯式呼叫該方法清除執行緒的區域性變數並不是必須的操作,但它可以加快記憶體回收的速度; 
protected Object initialValue() 

   返回該執行緒區域性變數的初始值,該方法是一個protected的方法,顯然是為了讓子類覆蓋而設計的。這個方法是一個延遲呼叫方法,線上程第1次呼叫get()或set(Object)時才執行,並且僅執行1次。ThreadLocal中的預設實現直接返回一個null。 

值得一提的是,在JDK5.0中,ThreadLocal已經支援泛型,該類的類名已經變為ThreadLocal<T>。API方法也相應進行了調整,新版本的API方法分別是void set(T value)、T get()以及T initialValue()。 

ThreadLocal是如何做到為每一個執行緒維護變數的副本的呢?其實實現的思路很簡單:在ThreadLocal類中有一個Map,用於儲存每一個執行緒的變數副本,Map中元素的鍵為執行緒物件,而值對應執行緒的變數副本。 


TheadLocal例項 

Java程式碼  收藏程式碼
  1. public class SequenceNumber {  
  2. //  ①通過匿名內部類覆蓋ThreadLocal的initialValue()方法,指定初始值    
  3.     private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>() {  
  4.         public Integer initialValue() {  
  5.             return 0;  
  6.         }  
  7.     };  
  8.     public int getNextNum() {  
  9.         seqNum.set(seqNum.get()+1);  
  10.         return seqNum.get();  
  11.     }  
  12.     public static void main(String [] args) {  
  13.         SequenceNumber sn = new SequenceNumber();  
  14. //      ③ 3個執行緒共享sn,各自產生序列號    
  15.         TestClient tc1 = new TestClient(sn);  
  16.         TestClient tc2 = new TestClient(sn);  
  17.         TestClient tc3 = new TestClient(sn);  
  18.         tc1.start();  
  19.         tc2.start();  
  20.         tc3.start();  
  21.     }  
  22. }  


Java程式碼  收藏程式碼
  1. public class TestClient extends Thread {  
  2.     private SequenceNumber sn;  
  3.     public TestClient(SequenceNumber sn) {  
  4.         this.sn = sn;  
  5.     }  
  6.     public void run() {  
  7. //      ④每個執行緒打出3個序列值    
  8.         for(int i = 0; i<3; i++) {  
  9.             System.out.println("thread[" + Thread.currentThread().getName() + "] sn[" + sn.getNextNum() +"]");  
  10.         }  
  11.     }  
  12. }  


執行結果 

thread[Thread-1] sn[1] 
thread[Thread-0] sn[1] 
thread[Thread-2] sn[1] 
thread[Thread-1] sn[2] 
thread[Thread-0] sn[2] 
thread[Thread-2] sn[2] 
thread[Thread-1] sn[3] 
thread[Thread-0] sn[3] 
thread[Thread-2] sn[3] 

考查輸出的結果資訊,我們發現每個執行緒所產生的序號雖然都共享同一個Sequence Number例項,但它們並沒有發生相互干擾的情況,而是各自產生獨立的序列號,這是因為我們通過ThreadLocal為每一個執行緒提供了單獨的副本。 

與Thread同步機制的比較 

ThreadLocal和執行緒同步機制相比有什麼優勢呢?ThreadLocal和執行緒同步機制都是為了解決多執行緒中相同變數的訪問衝突問題。 

在同步機制中,通過物件的鎖機制保證同一時間只有一個執行緒訪問變數。這時該變數是多個執行緒共享的,使用同步機制要求程式縝密地分析什麼時候對變數進行讀寫,什麼時候需要鎖定某個物件,什麼時候釋放物件鎖等繁雜的問題,程式設計和編寫難度相對較大。 

而ThreadLocal則從另一個角度來解決多執行緒的併發訪問。ThreadLocal為每一個執行緒提供一個獨立的變數副本,從而隔離了多個執行緒對訪問資料的衝突。因為每一個執行緒都擁有自己的變數副本,從而也就沒有必要對該變數進行同步了。ThreadLocal提供了執行緒安全的物件封裝,在編寫多執行緒程式碼時,可以把不安全的變數封裝進ThreadLocal。 

由於ThreadLocal中可以持有任何型別的物件,低版本JDK所提供的get()返回的是Object物件,需要強制型別轉換。但JDK 5.0通過泛型很好的解決了這個問題,在一定程度上簡化ThreadLocal的使用,程式碼清單9-2就使用了JDK 5.0新的ThreadLocal<T>版本。 

概括
對於多執行緒資源共享的問題,同步機制採用了“以時間換空間”的方式:訪問序列化,物件共享化。而ThreadLocal採用了“以空間換時間”的方式:訪問並行化,物件獨享化。前者僅提供一份變數,讓不同的執行緒排隊訪問,而後者為每一個執行緒都提供了一份變數,因此可以同時訪問而互不影響。 

本文參考:http://www.iteye.com/topic/1123824