ThreadLocal爲解決多線程程序的併發問題提供了一種新的思路。JDK 1.2的版本中就提供java.lang.ThreadLocal,使用這個工具類可以很簡潔地編寫出優美的多線程程序,ThreadLocal並不是一個Thread,而是Thread的局部變量。
1.下圖和輔助代碼解釋ThreadLocal的作用和目的:用於實現線程內的數據共享,即對於相同的程序代碼,多個模塊在同一個線程中運行時要共享一份數據,而在另外線程中運行時又共享另外一份數據。
2.每個線程調用全局ThreadLocal對象的set方法,就相當於往其內部的map中增加一條記錄,key分別是各自的線程,value是各自的set方法傳進去的值。在線程結束時可以調用ThreadLocal.clear()方法,這樣會更快釋放內存,不調用也可以,因爲線程結束後也可以自動釋放相關的ThreadLocal變量。
3.ThreadLocal的應用場景:
(1)訂單處理包含一系列操作:減少庫存量、增加一條流水臺賬、修改總賬,這幾個操作要在同一個事務中完成,通常也即同一個線程中進行處理,如果累加公司應收款的操作失敗了,則應該把前面的操作回滾,否則,提交所有操作,這要求這些操作使用相同的數據庫連接對象,而這些操作的代碼分別位於不同的模塊類中。
(2)銀行轉賬包含一系列操作: 把轉出帳戶的餘額減少,把轉入帳戶的餘額增加,這兩個操作要在同一個事務中完成,它們必須使用相同的數據庫連接對象,轉入和轉出操作的代碼分別是兩個不同的帳戶對象的方法。
(3)例如Strut2的ActionContext,同一段代碼被不同的線程調用運行時,該代碼操作的數據是每個線程各自的狀態和數據,對於不同的線程來說,getContext方法拿到的對象都不相同,對同一個線程來說,不管調用getContext方法多少次和在哪個模塊中getContext方法,拿到的都是同一個。
4.實驗案例:定義一個全局共享的ThreadLocal變量,然後啓動多個線程向該ThreadLocal變量中存儲一個隨機值,接着各個線程調用另外其他多個類的方法,這多個類的方法中讀取這個ThreadLocal變量的值,就可以看到多個類在同一個線程中共享同一份數據。
5.實現對ThreadLocal變量的封裝,讓外界不要直接操作ThreadLocal變量。
(1)對基本類型的數據的封裝,這種應用相對很少見。
(2)對對象類型的數據的封裝,比較常見,即讓某個類針對不同線程分別創建一個獨立的實例對象。
例子程序:
第一種實現方式:(但是這種實現方式不如第二種實現方式好)
package cn.itcast.lishehe;
import java.util.Random;
/** 李社河-2015年6月11日
* 題目要求:構造兩線程,要求:
* (1)兩線程併發操作 (這就要求不能使用syschronized關鍵字)
* (2)要求兩線程分別訪問各自的數據MyData對象,互不干擾
* (這裏就可以使用ThreadLocal對象,通過set()和get()即可獲得與本線程相關的MyData對象,但注意,其只能關聯一個數據,
* 所以對於多個數據則應該封裝到一個類中,其實際上也是通過Map實現的)
* (3)線程內有A、B兩個模塊,模塊之間共享數據MyData數據
*
* 本程序實現方式不如ThreadLocalDataIndependent2.java好。
**/
public class ThreadLocalDataIndependent1 {
static ThreadLocal<MyData> threadLocal = new ThreadLocal<MyData>();
public static void main(String[] args) {
for (int i = 0; i < 2; i++) {
new Thread(new Runnable() {
@Override
public void run() {
int data = new Random().nextInt(); //這裏的data必須定義爲局部變量,否則線程間不能實現數據獨立
MyData myData = new MyData();
myData.setName("name"+data);
myData.setAge(data);
threadLocal.set(myData);
new A().get();
new B().get();
}
}).start();
}
}
static class A{
public void get(){
MyData myData = threadLocal.get();
System.out.println("A from "+Thread.currentThread().getName()+" get MyData :"
+myData.getName()+","+myData.getAge());
}
}
static class B{
public void get(){
MyData myData = threadLocal.get();
System.out.println("B from "+Thread.currentThread().getName()+" get MyData :"
+myData.getName()+","+myData.getAge());
}
}
}
class MyData{
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
運行結果
package cn.itcast.lishehe;
import java.util.Random;
/** 李社河-2015年6月11日
* 題目要求:構造兩線程,要求:
* (1)兩線程併發操作 (這就要求不能使用syschronized關鍵字)
* (2)要求兩線程分別訪問各自的數據MyData對象,互不干擾
* (這裏就可以使用ThreadLocal對象,通過set()和get()即可獲得與本線程相關的MyData對象,但注意,其只能關聯一個數據,
* 所以對於多個數據則應該封裝到一個類中,其實際上也是通過Map實現的)
* (3)線程內有A、B兩個模塊,模塊之間共享數據MyData數據
*
* 本程序實現方式比ThreadLocalDataIndependent1.java要好。
* 其中Struts2對於用戶的每次請求,都將創建一個action實例進行處理,每個線程都有其獨立的數據,其實現方式就是這種。
**/
public class ThreadLocalDataIndependent2 {
public static void main(String[] args) {
for (int i = 0; i < 2; i++) {
new Thread(new Runnable() {
@Override
public void run() {
int data = new Random().nextInt(); //這裏的data必須定義爲局部變量,否則線程間不能實現數據獨立
MyData2.getInstance().setName("name"+data);
MyData2.getInstance().setAge(data);
new A().get();
new B().get();
}
}).start();
}
}
static class A{
public void get(){
MyData2 myData = MyData2.getInstance();
System.out.println("A from "+Thread.currentThread().getName()+" get MyData :"
+myData.getName()+","+myData.getAge());
}
}
static class B{
public void get(){
MyData2 myData = MyData2.getInstance();
System.out.println("B from "+Thread.currentThread().getName()+" get MyData :"
+myData.getName()+","+myData.getAge());
}
}
}
class MyData2{
private static ThreadLocal<MyData2> threadMap = new ThreadLocal<MyData2>();
private MyData2(){
}
//這裏無需使用syschronized關鍵字
public static MyData2 getInstance(){
MyData2 myData = threadMap.get();
if(myData==null){
myData = new MyData2();
threadMap.set(myData);
}
return myData;
}
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
運行結果