Java多執行緒2-多個執行緒之間共享資料
執行緒範圍的共享變數
多個業務模組針對同一個static變數的操作 要保證在不同執行緒中 各模組操作的是自身對應的變數物件
public class ThreadScopeSharaData { private staticint data = 0 ; public static void main(String[] args) { for(int i = 0 ;i<2 ;i++){ new Thread(new Runnable(){ @Override public void run() { data = new Random().nextInt(); System.out.println(Thread.currentThread().getName()+ " put random data:"+data); new A().get() ; new B().get() ; } }).start() ; } } static class A { public int get(){ System.out.println("A from " + Thread.currentThread().getName() + " get data :" + data); return data ; } } static class B{ public int get(){ System.out.println("B from " + Thread.currentThread().getName() + " get data :" + data); return data ; } } }
模組A ,B都需要訪問static的變數data 線上程0中會隨機生成一個data值 假設為10 那麼此時模組A和模組B線上程0中得到的data的值為10 ;線上程1中 假設會為data賦值為20 那麼在當前執行緒下
模組A和模組B得到data的值應該為20
看程式執行的結果:
Thread-0 put random data:-2009009251 Thread-1 put random data:-2009009251 A from Thread-0 get data :-2009009251 A from Thread-1 get data :-2009009251 B from Thread-0 get data :-2009009251 B from Thread-1 get data :-2009009251
Thread-0 put random data:-2045829602 Thread-1 put random data:-1842611697 A from Thread-0 get data :-1842611697 A from Thread-1 get data :-1842611697 B from Thread-0 get data :-1842611697 B from Thread-1 get data :-1842611697
會出現兩種情況
1.由於執行緒執行速度,新的隨機值將就的隨機值覆蓋 data 值一樣
2.data 值不一樣,但 A、B執行緒都
1.使用Map實現執行緒範圍內資料的共享
可是將data資料和當前允許的執行緒繫結在一塊,在模組A和模組B去獲取資料data的時候 是通過當前所屬的執行緒去取得data的結果就行了。
宣告一個Map集合 集合的Key為Thread 儲存當前所屬執行緒 Value 儲存data的值,程式碼如下:
public class ThreadScopeSharaData { private static Map<Thread, Integer> threadData = new HashMap<>(); 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(); System.out.println(Thread.currentThread().getName() + " put random data:" + data); threadData.put(Thread.currentThread(), data); new A().get(); new B().get(); } }).start(); } } static class A { public void get() { int data = threadData.get(Thread.currentThread()); System.out.println("A from " + Thread.currentThread().getName() + " get data:" + data); } } static class B { public void get() { int data = threadData.get(Thread.currentThread()); System.out.println("B from " + Thread.currentThread().getName() + " get data:" + data); } } }
Thread-0 put random data:-123490895 Thread-1 put random data:-1060992440 A from Thread-0 get data:-123490895 A from Thread-1 get data:-1060992440 B from Thread-0 get data:-123490895 B from Thread-1 get data:-1060992440

2.ThreadLocal實現執行緒範圍內資料的共享
(1)訂單處理包含一系列操作:減少庫存量、增加一條流水臺賬、修改總賬,這幾個操作要在同一個事務中完成,通常也即同一個執行緒中進行處理,如果累加公司應收款的操作失敗了,則應該把前面的操作回滾,否則,提交所有操作,這要求這些操作使用相同的資料庫連線物件,而這些操作的程式碼分別位於不同的模組類中。
(2)銀行轉賬包含一系列操作: 把轉出帳戶的餘額減少,把轉入帳戶的餘額增加,這兩個操作要在同一個事務中完成,它們必須使用相同的資料庫連線物件,轉入和轉出操作的程式碼分別是兩個不同的帳戶物件的方法。
(3)例如Strut2的ActionContext,同一段程式碼被不同的執行緒呼叫執行時,該程式碼操作的資料是每個執行緒各自的狀態和資料,對於不同的執行緒來說,getContext方法拿到的物件都不相同,對同一個執行緒來說,不管呼叫getContext方法多少次和在哪個模組中getContext方法,拿到的都是同一個。
4.實驗案例:定義一個全域性共享的ThreadLocal變數,然後啟動多個執行緒向該ThreadLocal變數中儲存一個隨機值,接著各個執行緒呼叫另外其他多個類的方法,這多個類的方法中讀取這個ThreadLocal變數的值,就可以看到多個類在同一個執行緒中共享同一份資料。
5.實現對ThreadLocal變數的封裝,讓外界不要直接操作ThreadLocal變數。
(1)對基本型別的資料的封裝,這種應用相對很少見。
(2)對物件型別的資料的封裝,比較常見,即讓某個類針對不同執行緒分別建立一個獨立的例項物件。
public class ThreadLocalTest { private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>(); 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(); System.out.println(Thread.currentThread().getName() + " put random data:" + data); threadLocal.set(data); new A().get(); new B().get(); } }).start(); } } static class A { public void get() { int data = threadLocal.get(); System.out.println("A from " + Thread.currentThread().getName() + " get data:" + data); } } static class B { public void get() { int data = threadLocal.get(); System.out.println("B from " + Thread.currentThread().getName() + " get data:" + data); } } }
Thread-0 put random data:-2015900409 Thread-1 put random data:-645411160 A from Thread-0 get data:-2015900409 A from Thread-1 get data:-645411160 B from Thread-0 get data:-2015900409 B from Thread-1 get data:-645411160
優化
public class ThreadLocalTest { private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>(); //private static ThreadLocal<MyThreadScopeData> myThreadScopeDataThreadLocal = new ThreadLocal<>(); 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(); System.out.println(Thread.currentThread().getName() + " put random data:" + data); threadLocal.set(data); //MyThreadScopeData myThreadScopeData = new MyThreadScopeData(); //myThreadScopeData.setName("name" + data); //myThreadScopeData.setAge(data); //myThreadScopeDataThreadLocal.set(myThreadScopeData); //獲取與當前執行緒繫結的例項並設定值 MyThreadScopeData.getThreadInstance().setName("name" + data); MyThreadScopeData.getThreadInstance().setAge(data); new A().get(); new B().get(); } }).start(); } } static class A { public void get() { int data = threadLocal.get(); //MyThreadScopeData myData = myThreadScopeDataThreadLocal.get(); // // //System.out.println("A from " + Thread.currentThread().getName() //+ " getMyData: " + myData.getName() + "," + myData.getAge()); MyThreadScopeData myData = MyThreadScopeData.getThreadInstance(); System.out.println("A from " + Thread.currentThread().getName() + " getMyData: " + myData.getName() + "," + myData.getAge()); } } static class B { public void get() { int data = threadLocal.get(); //System.out.println("B from " + Thread.currentThread().getName() + " get data:" + data); MyThreadScopeData myData = MyThreadScopeData.getThreadInstance(); System.out.println("B from " + Thread.currentThread().getName() + " getMyData: " + myData.getName() + "," + myData.getAge()); } } } //一個綁定當前執行緒的類 class MyThreadScopeData { private static ThreadLocal<MyThreadScopeData> map = new ThreadLocal<>(); private String name; private int age; private MyThreadScopeData() { } //定義一個靜態方法,返回各執行緒自己的例項 //這裡不必用同步,因為每個執行緒都要建立自己的例項,所以沒有執行緒安全問題。 public static MyThreadScopeData getThreadInstance() { //獲取當前執行緒繫結的例項 MyThreadScopeData instance = map.get(); if (instance == null) { instance = new MyThreadScopeData(); map.set(instance); } return instance; } 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; } }
Thread-1 put random data:-1041517189 Thread-0 put random data:-98835751 A from Thread-1 getMyData: name-1041517189,-1041517189 A from Thread-0 getMyData: name-98835751,-98835751 B from Thread-1 getMyData: name-1041517189,-1041517189 B from Thread-0 getMyData: name-98835751,-98835751
例項:
設計4個執行緒,其中兩個執行緒每次對j增加1,另外兩個執行緒對j每次減少1,寫出程式。
1、如果每個執行緒執行的程式碼相同,可以使用同一個Runnable物件,這個Runnable物件中有那個共享資料,例如,賣票系統就可以這麼做。
public class SellTicket { //賣票系統,多個視窗的處理邏輯是相同的 public static void main(String[] args) { Ticket t = new Ticket(); new Thread(t).start(); new Thread(t).start(); } } /** * 將屬性和處理邏輯,封裝在一個類中 * * @author yang */ class Ticket implements Runnable { private int ticket = 10; public synchronized void run() { while (ticket > 0) { ticket--; System.out.println("當前票數為:" + ticket); } } }
2、如果每個執行緒執行的程式碼不同,這時候需要用不同的Runnable物件,例如,設計2個執行緒。一個執行緒對j增加1,另外一個執行緒對j減1,銀行存取款系統。
public class MultiThreadShareData { private int j; public static void main(String[] args) { MultiThreadShareData multiThreadShareData = new MultiThreadShareData(); for(int i=0;i<2;i++){ new Thread(multiThreadShareData.new ShareData1()).start();//增加 new Thread(multiThreadShareData.new ShareData2()).start();//減少 } } //自增 private synchronized void Inc(){ j++; System.out.println(Thread.currentThread().getName()+" inc "+j); } //自減 private synchronized void Dec(){ j--; System.out.println(Thread.currentThread().getName()+" dec "+j); } class ShareData1 implements Runnable { public void run() { for(int i=0;i<5;i++){ Inc(); } } } class ShareData2 implements Runnable { public void run() { for(int i=0;i<5;i++){ Dec(); } } } }
Thread-0 inc 1 Thread-0 inc 2 Thread-0 inc 3 Thread-0 inc 4 Thread-0 inc 5 Thread-1 dec 4 Thread-1 dec 3 Thread-2 inc 4 Thread-2 inc 5 Thread-2 inc 6 Thread-2 inc 7 Thread-2 inc 8 Thread-1 dec 7 Thread-1 dec 6 Thread-1 dec 5 Thread-3 dec 4 Thread-3 dec 3 Thread-3 dec 2 Thread-3 dec 1 Thread-3 dec 0