ThreadLocal是什么

   ThreadLocal是thread local variable(線程局部變量)。線程局部變量(ThreadLocal)其實的功用非常簡單,就是為每一個使用該變量的線程都提供一個變量值的副本,是每一個線程都可以獨立地改變自己的副本,而不會和其它線程的副本沖突。每個線程只能看到與自己相聯系的值,而不知道別的線程可能正在使用或修改它們自己的副本。從線程的角度看,就好像每一個線程都完全擁有該變量。
 ThreadLocal的設計

   首先看看ThreadLocal的接口:

public class ThreadLocal {
            
public Object get();
            
public void set(Object newValue);
            
public Object initialValue();
            }


       Object get() ; // 返回當前線程的線程局部變量副本
           protected Object initialValue(); // 返回該線程局部變量的當前線程的初始值
   void set(Object value); // 設置當前線程的線程局部變量副本的值

   ThreadLocal有3個方法,其中值得注意的是initialValue(),該方法是一個protected的方法,顯然是為了子類重寫而特意實現的。該方法返回當前線程在該線程局部變量的初始值,這個方法是一個延遲調用方法,在一個線程第1次調用get()或者set(Object)時才執行,并且僅執行1次。ThreadLocal中的確實實現直接返回一個null:

protected Object initialValue() { return null; }

  ThreadLocal是如何做到為每一個線程維護變量的副本的呢?其實實現的思路很簡單,在ThreadLocal類中有一個Map,用于存儲每一個線程的變量的副本。比如下面的示例實現:
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
  }

}

這個實現的性能不會很好,因為每個 get()set() 操作都需要 values 映射表上的同步,而且如果多個線程同時訪問同一個 ThreadLocal ,那么將發生爭用。此外,這個實現也是不切實際的,因為用 Thread 對象做 values 映射表中的關鍵字將導致無法在線程退出后對 Thread 進行垃圾回收,而且也無法對死線程的 ThreadLocal 的特定于線程的值進行垃圾回收。




但JDK中的ThreadLocal的實現總體思路也類似于此。




  j2EE中的Thread

         Web容器中有三個周期request/Httpsession/application
         其中request是客戶端發出的一個請求,這個request的載體就是一個線程,實際等同于一個線程的生命周期。Request是封裝在線程上面一個抽象概念。而ThreadLocal則相當于多個線程的一個共享全局變量存儲地,它里面保存的是和每個線程相關的狀態。所以threadLocal是為線程服務的,和線程處于一個底層位置。

         正因為Spring/Hibernate這些框架對于狀態處理短處,所以才只能透過Web容器的request等狀態封裝,直接到底層操作與線程同一層次的threadLocal。

         當一個線程或request結束時,threadlocal中的狀態就沒有了,所以threadLocal基本類似request.setAttribute作用,threadLocal中的對象狀態的生命周期等同于request.


 ThreadLocal的使用



用 ThreadLocal 實現每線程 Singleton

線程局部變量常被用來描繪有狀態“單子”(Singleton) 或線程安全的共享對象,或者是通過把不安全的整個變量封裝進 ThreadLocal ,或者是通過把對象的特定于線程的狀態封裝進 ThreadLocal 。例如,在與數據庫有緊密聯系的應用程序中,程序的很多方法可能都需要訪問數據庫。在系統的每個方法中都包含一個 Connection 作為參數是不方便的 — 用“單子”來訪問連接可能是一個雖然更粗糙,但卻方便得多的技術。然而,多個線程不能安全地共享一個 JDBC Connection 。如清單 3 所示,通過使用“單子”中的 ThreadLocal ,我們就能讓我們的程序中的任何類容易地獲取每線程 Connection 的一個引用。這樣,我們可以認為 ThreadLocal 允許我們創建 每線程單子


清單 3. 把一個 JDBC 連接存儲到一個每線程 Singleton 中

 

 public class ConnectionDispenser {
            
private static class ThreadLocalConnection extends ThreadLocal {
            
public Object initialValue() {
            
return DriverManager.getConnection(ConfigurationSingleton.getDbUrl());
            }

            }

            
private ThreadLocalConnection conn = new ThreadLocalConnection();
            
public static Connection getConnection() {
            
return (Connection) conn.get();
            }

            }

            


任何創建的花費比使用的花費相對昂貴些的有狀態或非線程安全的對象,例如 JDBC Connection 或正則表達式匹配器,都是可以使用每線程單子(singleton)技術的好地方。當然,在類似這樣的地方,您可以使用其它技術,例如用池,來安全地管理共享訪問。然而,從可伸縮性角度看,即使是用池也存在一些潛在缺陷。因為池實現必須使用同步,以維護池數據結構的完整性,如果所有線程使用同一個池,那么在有很多線程頻繁地對池進行訪問的系統中,程序性能將因爭用而降低。
 


用 ThreadLocal 簡化調試日志紀錄

其它適合使用 ThreadLocal 但用池卻不能成為很好的替代技術的應用程序包括存儲或累積每線程上下文信息以備稍后檢索之用這樣的應用程序。例如,假設您想創建一個用于管理多線程應用程序調試信息的工具。您可以用如清單 4 所示的 DebugLogger 類作為線程局部容器來累積調試信息。在一個工作單元的開頭,您清空容器,而當一個錯誤出現時,您查詢該容器以檢索這個工作單元迄今為止生成的所有調試信息。


清單 4. 用 ThreadLocal 管理每線程調試日志

 

   public class DebugLogger {
            
private static class ThreadLocalList extends ThreadLocal {
            
public Object initialValue() {
            
return new ArrayList();
            }

            
public List getList() {
            
return (List) super.get();
            }

            }

            
private ThreadLocalList list = new ThreadLocalList();
            
private static String[] stringArray = new String[0];
            
public void clear() {
            list.getList().clear();
            }

            
public void put(String text) {
            list.getList().add(text);
            }

            
public String[] get() {
            
return list.getList().toArray(stringArray);
            }

            }

            


在您的代碼中,您可以調用 DebugLogger.put() 來保存您的程序正在做什么的信息,而且,稍后如果有必要(例如發生了一個錯誤),您能夠容易地檢索與某個特定線程相關的調試信息。 與簡單地把所有信息轉儲到一個日志文件,然后努力找出哪個日志記錄來自哪個線程(還要擔心線程爭用日志紀錄對象)相比,這種技術簡便得多,也有效得多。

ThreadLocal 在基于 servlet 的應用程序或工作單元是一個整體請求的任何多線程應用程序服務器中也是很有用的,因為在處理請求的整個過程中將要用到單個線程。您可以通過前面講述的每線程單子技術用 ThreadLocal 變量來存儲各種每請求(per-request)上下文信息。






   如果希望線程局部變量初始化其它值,那么需要自己實現ThreadLocal的子類并重寫該方法,通常使用一個內部匿名類對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();
  }
}

  SerialNum類的使用將非常地簡單,因為get()方法是static的,所以在需要獲取當前線程的序號時,簡單地調用:

int serial = SerialNum.get();

  即可。

   在線程是活動的并且ThreadLocal對象是可訪問的時,該線程就持有一個到該線程局部變量副本的隱含引用,當該線程運行結束后,該線程擁有的所以線程局部變量的副本都將失效,并等待垃圾收集器收集。

   ThreadLocal與其它同步機制的比較

   ThreadLocal和其它同步機制相比有什么優勢呢?ThreadLocal和其它所有的同步機制都是為了解決多線程中的對同一變量的訪問沖突,在普通的同步機制中,是通過對象加鎖來實現多個線程對同一變量的安全訪問的。這時該變量是多個線程共享的,使用這種同步機制需要很細致地分析在什么時候對變量進行讀寫,什么時候需要鎖定某個對象,什么時候釋放該對象的鎖等等很多。所有這些都是因為多個線程共享了資源造成的。ThreadLocal就從另一個角度來解決多線程的并發訪問,ThreadLocal會為每一個線程維護一個和該線程綁定的變量的副本,從而隔離了多個線程的數據,每一個線程都擁有自己的變量副本,從而也就沒有必要對該變量進行同步了。ThreadLocal提供了線程安全的共享對象,在編寫多線程代碼時,可以把不安全的整個變量封裝進ThreadLocal,或者把該對象的特定于線程的狀態封裝進ThreadLocal。

   由于ThreadLocal中可以持有任何類型的對象,所以使用ThreadLocal get當前線程的值是需要進行強制類型轉換。但隨著新的Java版本(1.5)將模版的引入,新的支持模版參數的ThreadLocal<T>類將從中受益。也可以減少強制類型轉換,并將一些錯誤檢查提前到了編譯期,將一定程度地簡化ThreadLocal的使用。

   總結

   當然ThreadLocal并不能替代同步機制,兩者面向的問題領域不同。同步機制是為了同步多個線程對相同資源的并發訪問,是為了多個線程之間進行通信的有效方式;而ThreadLocal是隔離多個線程的數據共享,從根本上就不在多個線程之間共享資源(變量),這樣當然不需要對多個線程進行同步了。所以,如果你需要進行多個線程之間進行通信,則使用同步機制;如果需要隔離多個線程之間的共享沖突,可以使用ThreadLocal,這將極大地簡化你的程序,使程序更加易讀、簡潔。


實例:
public class Hello implements Runnable{
    
static class Counter{
        
int c = 0;
        
public void Count(){
            System.
out.println(Thread.currentThread().hashCode() + ""+ c++);
        }

    }

    
private static ThreadLocal counter = new ThreadLocal(){
        
protected Object initialValue() {return new Counter();}
    }
;
    
public static Counter Counter(){return (Counter)counter.get();} 
    
    
public static void main(String[] args)throws Exception {
        Hello h 
= new Hello();
        h.run();
        
new Thread(new Hello()).start();
        Thread.sleep(
1000L);
        h.run();
        Counter().Count();
    }


    
public void run() {
        Counter().Count();
        Counter().Count();
        Counter().Count();
    }

}


結果:
253585550
253585551
253585552
263995540
263995541
263995542
253585553
253585554
253585555
253585556

看樣ThreadLocal工作的很好.請注意ThreadLocal類似一個訪問媒介,get()返回的值是存儲在當前線程中的.

值得注意的是InheritableThreadLocal類,可以使ThreadLocal數據傳遞到子線程中.

另外servlet線程可能會被重新利用,要注意在每個servlet開始時重新初始化.