多線程(三) 實現線程範圍內模塊之間共享數據及線程間數據獨立(ThreadLocal)

       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;  
    }  
      
}  



運行結果


 第二種實現方式:(struts2中對於用戶的每次請求創建一個action實例,其也是這樣實現的)


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;  
    }  
      
}  

運行結果


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章