Java高并发编程:线程范围内共享数据
筆記摘要
所謂線程范圍內共享數據,即對于相同的程序代碼,多個模塊在同一個線程中運行時要共享一份數據,而在另外線程中運行時又共享另外一份數據,API中為我們提供了一個操作線程范圍內共享數據的類ThreadLocal,對于線程范圍內共享數據的應用,在ThreadLocal的應用場景中進行了介紹,然后主要對它的使用進行講解,演示了由單一數據的共享到將多個數據封裝到一個對象中,然后進行共享。在開始先用一個Map集合簡單實現線程范圍內數據的共享
1. 使用Map實現線程范圍內數據的共享
原理:將線程對象作為map的鍵存入,這樣就保證了map對象的唯一,也就保證了線程內數據的唯一
關鍵: 明確一點,把當前線程對象作為map集合的鍵存進去
import java.util.HashMap; import java.util.Map; import java.util.Random; public class ThreadScopeShareData { private static int data = 0; //定義一個全局的成員變量 private static Map<Thread, Integer> threadData = new HashMap<Thread, Integer>(); 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() + " has put data :" + data); //把當前線程對象作為鍵,就可以保證map對象的唯一,即保證線程內的數據唯一 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 has put data:-49248136 Thread-1 has put data:311124475 A from Thread-0 get data:-49248136 A from Thread-1 get data:311124475 B from Thread-0 get data:-49248136 B from Thread-1 get data:3111244752. ThreadLocal類
ThreadLocal的作用和目的:
用于實現線程內的數據共享,即對于相同的程序代碼,多個模塊在同一個線程中運行時要共享一份數據,而在另外線程中運行時又共享另外一份數據。
每個線程調用全局ThreadLocal對象的set方法,就相當于往其內部的map中增加一條記錄,key分別是各自的線程,value是各自的set方法傳進去的值。在線程結束時可以調用ThreadLocal.clear()方法,這樣會更快釋放內存,不調用也可以,因為線程結束后也可以自動釋放相關的ThreadLocal變量。
3. ThreadLocal的應用場景
1、訂單處理包含一系列操作:減少庫存量、增加一條流水臺賬、修改總賬,這幾個操作要在同一個事務中完成,通常也即同一個線程中進行處理,如果累加公司應收款的操作失敗了,則應該把前面的操作回滾,否則,提交所有操作,這要求這些操作使用相同的數據庫連接對象,而這些操作的代碼分別位于不同的模塊類中。
2、 銀行轉賬包含一系列操作:把轉出帳戶的余額減少,把轉入帳戶的余額增加,這兩個操作要在同一個事務中完成,它們必須使用相同的數據庫連接對象,轉入和轉出操作的代碼分別是兩個不同的帳戶對象的方法。
3、例如Strut2的ActionContext,同一段代碼被不同的線程調用運行時,該代碼操作的數據是每個線程各自的狀態和數據,對于不同的線程來說,getContext方法拿到的對象都不相同,對同一個線程來說,不管調用getContext方法多少次和在哪個模塊中getContext方法,拿到的都是同一個。
線程范圍內共享數據示意圖
實現對ThreadLocal變量的封裝, 讓外界不要直接操作ThreadLocal變量由于對基本類型的數據的封裝,這種應用相對很少見。而對對象類型的數據的封裝,比較常見,即讓某個類針對不同線程分別創建一個獨立的實例對象。所以我們要對數據進行封裝。
實現方式一
示例說明:
1、 該示例包含了對基本類型數據的共享和對象類型數據的共享
2、定義一個全局共享的ThreadLocal變量,然后啟動多個線程向該ThreadLocal變量中存儲一個隨機值,接著各個線程調用另外其他多個類的方法,這多個類的方法中讀取這個ThreadLocal變量的值,就可以看到多個類在同一個線程中共享同一份數據。
3、但這里每次存儲數據時,都是使用同一個ThreadLocal對象,只是重新賦值而已
import java.util.HashMap; import java.util.Map; import java.util.Random; public class ThreadLocalTest { private static ThreadLocal<Integer> x = new ThreadLocal<Integer>(); //創建一個存儲封裝類對象的ThreadLocal private static ThreadLocal<MyThreadScopeData> myThreadScopeData = new ThreadLocal<MyThreadScopeData>(); private static int data = 0; 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()+"has put data : "+data); x.set(data); //共享多個數據 //將數據封裝在myData對象中,并將myData作為myThreadScopeData的鍵 MyThreadScopeData myData = new MyThreadScopeData(); myData.setName("name "+data); myData.setAge(data); myThreadScopeData.set(myData); new A().get(); new B().get(); } }).start(); } } static class A{ public void get(){ int data = x.get(); System.out.println("A from "+Thread.currentThread().getName()+" get data :"+data); //從myData中取出數據,并獲取當前線程名,數據 MyThreadScopeData myData = myThreadScopeData.get(); System.out.println("A from "+Thread.currentThread().getName()+" getMyData: " + myData.getName() + "," +myData.getAge()); } } static class B{ public void get(){ int data = x.get(); System.out.println("B from "+Thread.currentThread().getName()+" get data :"+data); MyThreadScopeData myData = myThreadScopeData.get(); System.out.println("B from "+Thread.currentThread().getName()+" getMyData: " + myData.getName() + "," +myData.getAge()); } } } //封裝數據的類 class MyThreadScopeData{ 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; } }輸出結果
Thread-0has put data : 1317043235 Thread-1has put data : -969579752 A from Thread-0 get data :1317043235 A from Thread-1 get data :-969579752 A from Thread-0 getMyData: name 1317043235,1317043235 A from Thread-1 getMyData: name -969579752,-969579752 B from Thread-0 get data :1317043235 B from Thread-1 get data :-969579752 B from Thread-0 getMyData: name 1317043235,1317043235 B from Thread-1 getMyData: name -969579752,-969579752實現方式二
示例說明:
這里模擬原始的單例模式,它們的區別是:單例模式中只有唯一的一個實例,而這里是每個線程擁有自己唯一的實例,只要是已經創建,就直接返回,保證每個線程擁有自己的唯一一份實例
優點:
這里可以返回每個線程自己唯一的實例對象,所以不必在外面定義,當在代碼中的任意地方想獲取到一個可以存儲自己數據的線程實例的時候直接去調用getThreadInstance方法即可,直接定義在數據對象的內部,和數據關系更緊密,而方式一,則每次想存入數據的時候都需要在外面創建一個ThreadLocal對象用于存儲數據。所以方式二更具封裝性。
package cn.itcast.heima2; import java.util.HashMap; import java.util.Map; import java.util.Random; public class ThreadLocalTest { //創建一個ThreadLocal對象 private static ThreadLocal<Integer> x = new ThreadLocal<Integer>(); 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() + " has put data :" + data); x.set(data); //往當前線程存入一條數據 //獲取與當前線程綁定的實例并設置值 MyThreadScopeData.getThreadInstance().setName("name:" + data); MyThreadScopeData.getThreadInstance().setAge(data); new A().get(); new B().get(); } }).start(); } } static class A{ public void get(){ int data = x.get(); //獲取當前線程中的數據 System.out.println("A from " + Thread.currentThread().getName() + " get data :" + data); //獲取與當前線程綁定的實例 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 = x.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() + ",age: " + myData.getAge()); } } } //一個綁定當前線程的類 class MyThreadScopeData{ private MyThreadScopeData(){} //構造方法私有化 private static ThreadLocal<MyThreadScopeData> map = new ThreadLocal<MyThreadScopeData>(); //定義一個靜態方法,返回各線程自己的實例 //這里不必用同步,因為每個線程都要創建自己的實例,所以沒有線程安全問題。 public static /*synchronized*/ MyThreadScopeData getThreadInstance(){ MyThreadScopeData instance = map.get(); //獲取當前線程綁定的實例 if(instance == null){ instance = new MyThreadScopeData(); map.set(instance); //創建完之后,將實例對象存進去 } return instance; } 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; } }輸出結果
Thread-0 has put data :2105117242 Thread-1 has put data :-368218341 A from Thread-1 get data :-368218341 A from Thread-1 getMyData: name:-368218341,-368218341 A from Thread-0 get data :2105117242 A from Thread-0 getMyData: name:2105117242,2105117242 B from Thread-0 get data :2105117242 B from Thread-1 get data :-368218341 B from Thread-0 getMyData: name:2105117242,age: 2105117242 B from Thread-1 getMyData: name:-368218341,age: -3682183414. 總結
一個ThreadLocal代表一個變量,故其中只能放一個數據,有兩個變量都要線程范圍內共享,則要定義兩個ThreadLocal對象,如果數據更多就很麻煩,可以先定義一個對象封裝變量,然后在ThreadLocal中存儲這一個對象,而這些操作都在提供線程數據類中完成
總結
以上是生活随笔為你收集整理的Java高并发编程:线程范围内共享数据的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java高并发编程:定时器、互斥、同步通
- 下一篇: Java高并发编程:线程池