早在JDK的版本中就提供了javalangThreadLocal為解決多線程程序的並發問題提供了一種新的思路使用這個工具類可以很簡潔地編寫出優美的多線程程序
ThreadLocal很容易讓人望文生義想當然地認為是一個本地線程其實ThreadLocal並不是一個Thread而是Thread的局部變量也許把它命名為ThreadLocalVariable更容易讓人理解一些當使用ThreadLocal維護變量時Threadlocal為每個使用該變量的線程提供獨立的變量副本所以每一個線程都可以獨立地改變自己的副本而不會影響其他線程所獨立的副本
從線程的角度看目標變量就是線程的本地變量這也是類名中Local所要表達的意思線程局部變量並不是Java的新發明很多語言(如IBM XL FORTRAN)在語法層面就提供了線程局部變量在Java中沒有提供語言級支持而是變相地通過ThreadLocal的類提供支持
JDK以後提供了泛型支持ThreadLocal被定義為支持泛型
public class ThreadLocal<T> extends Object
T為線程局部變量的類型該類定義了個方法
)protected T initialValue()返回此線程局部變量的當前線程的初始值線程的第一次使用get()方法訪問變量時將調用此方法但如果線程之前調用了set(T)方法則不會對該線程再調用initialValue方法通常此方法對每個線程最多調用一次但如果在調用get()後又調用了remove()則可能再次調用此方法
該實現返回null如果程序員希望線程局部變量具有null以外的值則必須為ThreadLocal創建子類並重寫此方法通常將使用匿名內部類完成此操作
)public Tget()返回此線程局部變量的當前線程副本中的值如果變量沒有用於當前線程的值則先將其初始化為調用initialValue方法返回的值
)public void Set(T value)將此線程局部變量的當前線程副本中的值設置為指定值大部分子類不需要重寫此方法它們只依靠initialValue()方法來設置線程局部變量的值
)public void remove()移除此線程局部變量當前線程的值如果此線程局部變量隨後被當前線程讀取且這期間當前線程沒有設置其值則將調用其initialValue()方法重新初始化其值這將導致在當前線程多次調用initialValue方法
下面是一個使用ThreadLocal的例子每個線程產生自己獨立的序列號就是使用Threadlocal存儲每個線程獨立的版本號副本線程之間互不干擾
public class SequenceNumber {
//定義匿名內部類創建ThreadLocal的變量
private static ThreadLocal<Integer> seqNum=new ThreadLocal<Integer>(){
//覆蓋初始化方法
public Integer initialValue(){
return ;
}
};
//下一個序列號
public int getNextNum(){
seqNumset(seqNumget()+)
return seqNumget()
}
private static class TestClient extends Thread{
private SequenceNumber sn;
public TestClient(SequenceNumber sn){
thissn=sn;
}
//產生序列號
public void run(){
for(int i=;i<;i++){
Systemoutprintln(Thread[+ThreadcurrentThread()getName()+
] sn[+sngetNextNum()+])
}
}
}
public static void main(String[] args) {
SequenceNumber seqNum=new SequenceNumber()
//三個線程產生各自的序列號
TestClient t=new TestClient(seqNum)
TestClient t=new TestClient(seqNum)
TestClient t=new TestClient(seqNum)
tstart()
tstart()
tstart()
}
}
public class SequenceNumber { //定義匿名內部類創建ThreadLocal的變量 private static ThreadLocal<Integer> seqNum=new ThreadLocal<Integer>(){ //覆蓋初始化方法 public Integer initialValue(){ return ; } }; //下一個序列號 public int getNextNum(){ seqNumset(seqNumget()+) return seqNumget() } private static class TestClient extends Thread{ private SequenceNumber sn; public TestClient(SequenceNumber sn){ thissn=sn; } //產生序列號 public void run(){ for(int i=;i<;i++){ Systemoutprintln(Thread[+ThreadcurrentThread()getName()+ ] sn[+sngetNextNum()+]) } } } public static void main(String[] args) { SequenceNumber seqNum=new SequenceNumber() //三個線程產生各自的序列號 TestClient t=new TestClient(seqNum) TestClient t=new TestClient(seqNum) TestClient t=new TestClient(seqNum) tstart() tstart() tstart() } }
程序的運行結果如下
Java代碼
Thread[Thread] sn[]
Thread[Thread] sn[]
Thread[Thread] sn[]
Thread[Thread] sn[]
Thread[Thread] sn[]
Thread[Thread] sn[]
Thread[Thread] sn[]
Thread[Thread] sn[]
Thread[Thread] sn[]
Thread[Thread] sn[] Thread[Thread] sn[] Thread[Thread] sn[] Thread[Thread] sn[] Thread[Thread] sn[] Thread[Thread] sn[] Thread[Thread] sn[] Thread[Thread] sn[] Thread[Thread] sn[]
從運行結果可以看出使用了ThreadLocal後每個線程產生了獨立的序列號沒有相互干擾通常我們通過匿名內部類的方式定義ThreadLocal的子類提供初始的變量值
ThreadLocal和線程同步機制相比有什麼優勢呢?ThreadLocal和線程同步機制都是為了解決多線程中相同變量的訪問沖突問題
在同步機制中通過對象的鎖機制保證同一時間只有一個線程訪問變量這時該變量是多個線程共享的使用同步機制要求程序缜密地分析什麼時候對變量進行讀寫什麼時候需要鎖定某個對象時候時候釋放對象鎖等繁雜的問題程序設計和編寫難度相對較大
而ThreadLocal則從另一個角度來解決多線程的並發訪問ThreadLocal會為每一個線程提供一個獨立的變量副本從而隔離了多個線程對數據的訪問沖突因為每一個線程都擁有自己的變量副本從而也就沒有必要對該變量進行同步了ThreadLocal提供了線程安全的共享對象在編寫多線程代碼時可以把不安全的變量封裝進ThreadLocal
概況起來說對於多線程資源共享的問題同步機制采用了以時間換空間的方式而ThreadLocal采用了以空間換時間的方式前者僅提供了一份變量讓不同的線程排隊訪問而後者為每一個線程都提供了一份變量因此可以同時訪問而互不影響
需要注意的是ThreadLocal對象是一個本質上存在風險的工具應該在完全理解將要使用的線程模型之後再去使用ThreadLocal對象這就引出了線程池(thread pool)的問題線程池是一種線程重用技術有了線程池就不必為每個任務創建線的線程一個線程可能會多次使用用於這種環境的任何ThreadLocal對象包含的都是最後使用該線程的代碼鎖設置的狀態而不是在開始執行新線程時所具有的未被初始化的狀態
google_protectAndRun(render_adsjs::google_render_ad google_handleError google_render_ad)
From:http://tw.wingwit.com/Article/program/Java/hx/201311/26985.html