Java 多線程之間共享數(shù)據(jù)
1、線程范圍的共享變量
多個業(yè)務(wù)模塊針對同一個static
變量的操作 要保證在不同線程中 各模塊操作的是自身對應(yīng)的變量對象
public class ThreadScopeSharaData { 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() { 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中會隨機(jī)生成一個data值 假設(shè)為10 那么此時模塊A和模塊B在線程0中得到的data
的值為10 ;在線程1中 假設(shè)會為data賦值為20 那么在當(dāng)前線程下
模塊A和模塊B得到data的值應(yīng)該為20
看程序執(zhí)行的結(jié)果:
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
會出現(xiàn)兩種情況:
- 1.由于線程執(zhí)行速度,新的隨機(jī)值將就的隨機(jī)值覆蓋 data 值一樣
- 2.data 值不一樣,但 A、B線程都
2、使用Map實(shí)現(xiàn)線程范圍內(nèi)數(shù)據(jù)的共享
可是將data數(shù)據(jù)和當(dāng)前允許的線程綁定在一塊,在模塊A和模塊B去獲取數(shù)據(jù)data的時候 是通過當(dāng)前所屬的線程去取得data的結(jié)果就行了。
聲明一個Map集合 集合的Key為Thread 存儲當(dāng)前所屬線程 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
3、ThreadLocal實(shí)現(xiàn)線程范圍內(nèi)數(shù)據(jù)的共享
(1)訂單處理包含一系列操作:減少庫存量、增加一條流水臺賬、修改總賬,這幾個操作要在同一個事務(wù)中完成,通常也即同一個線程中進(jìn)行處理,如果累加公司應(yīng)收款的操作失敗了,則應(yīng)該把前面的操作回滾,否則,提交所有操作,這要求這些操作使用相同的數(shù)據(jù)庫連接對象,而這些操作的代碼分別位于不同的模塊類中。
(2)銀行轉(zhuǎn)賬包含一系列操作: 把轉(zhuǎn)出帳戶的余額減少,把轉(zhuǎn)入帳戶的余額增加,這兩個操作要在同一個事務(wù)中完成,它們必須使用相同的數(shù)據(jù)庫連接對象,轉(zhuǎn)入和轉(zhuǎn)出操作的代碼分別是兩個不同的帳戶對象的方法。
(3)例如Strut2
的ActionContext
,同一段代碼被不同的線程調(diào)用運(yùn)行時,該代碼操作的數(shù)據(jù)是每個線程各自的狀態(tài)和數(shù)據(jù),對于不同的線程來說,getContext
方法拿到的對象都不相同,對同一個線程來說,不管調(diào)用getContext
方法多少次和在哪個模塊中getContext
方法,拿到的都是同一個。
(4)實(shí)驗(yàn)案例:定義一個全局共享的ThreadLocal
變量,然后啟動多個線程向該ThreadLocal變量中存儲一個隨機(jī)值,接著各個線程調(diào)用另外其他多個類的方法,這多個類的方法中讀取這個ThreadLocal
變量的值,就可以看到多個類在同一個線程中共享同一份數(shù)據(jù)。
(5)實(shí)現(xiàn)對ThreadLocal
變量的封裝,讓外界不要直接操作ThreadLocal
變量。
- 對基本類型的數(shù)據(jù)的封裝,這種應(yīng)用相對很少見。
- 對對象類型的數(shù)據(jù)的封裝,比較常見,即讓某個類針對不同線程分別創(chuàng)建一個獨(dú)立的實(shí)例對象。
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
4、優(yōu)化
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); //獲取與當(dāng)前線程綁定的實(shí)例并設(shè)置值 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()); } } } //一個綁定當(dāng)前線程的類 class MyThreadScopeData { private static ThreadLocal<MyThreadScopeData> map = new ThreadLocal<>(); private String name; private int age; private MyThreadScopeData() { } //定義一個靜態(tài)方法,返回各線程自己的實(shí)例 //這里不必用同步,因?yàn)槊總€線程都要創(chuàng)建自己的實(shí)例,所以沒有線程安全問題。 public static MyThreadScopeData getThreadInstance() { //獲取當(dāng)前線程綁定的實(shí)例 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
5、實(shí)例
設(shè)計(jì)4個線程,其中兩個線程每次對j增加1,另外兩個線程對j每次減少1,寫出程序。
(1)如果每個線程執(zhí)行的代碼相同,可以使用同一個Runnable
對象,這個Runnable對象中有那個共享數(shù)據(jù),例如,賣票系統(tǒng)就可以這么做。
public class SellTicket { //賣票系統(tǒng),多個窗口的處理邏輯是相同的 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("當(dāng)前票數(shù)為:" + ticket); } } }
(2)如果每個線程執(zhí)行的代碼不同,這時候需要用不同的Runnable
對象,例如,設(shè)計(jì)2個線程。一個線程對j增加1,另外一個線程對j減1,銀行存取款系統(tǒng)。
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
到此這篇關(guān)于Java 多線程之間共享數(shù)據(jù)的文章就介紹到這了,更多相關(guān)Java 多線程共享數(shù)據(jù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot整合Dubbo zookeeper過程解析
這篇文章主要介紹了SpringBoot整合Dubbo zookeeper過程解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-02-02Java基礎(chǔ)學(xué)習(xí)之構(gòu)造方法詳解
這篇文章主要為大家詳細(xì)介紹了Java基礎(chǔ)學(xué)習(xí)中構(gòu)造方法的概述及注意事項(xiàng),文中的示例代碼講解詳細(xì),對我們學(xué)習(xí)Java有一定幫助,需要的可以參考一下2022-08-08詳解Java如何實(shí)現(xiàn)一個優(yōu)秀的散列表
這篇文章主要通過簡單的示例為大家詳細(xì)介紹了在Java中如何實(shí)現(xiàn)一個優(yōu)秀的散列表,文中的示例代碼講解詳細(xì),具有一定的參考價值,需要的可以了解一下2023-07-07Spring在@ConditionalOnProperty注解使用詳解
這篇文章主要介紹了Spring在@ConditionalOnProperty注解使用詳解,@ConditionalOnProperty注解是Spring Boot的條件注解,主要用法是根據(jù)配置文件中的屬性來控制某個配置類是否生效,或者控制某個Bean是否被創(chuàng)建,需要的朋友可以參考下2023-11-11maven中自定義MavenArchetype的實(shí)現(xiàn)
Maven自身提供了許多Archetype來方便用戶創(chuàng)建Project,為了避免在創(chuàng)建project時重復(fù)的拷貝和修改,我們通過自定義Archetype來規(guī)范顯得還蠻有必要,下面就來介紹一下,感興趣的可以了解一下2025-01-01Springboot 使用maven release插件執(zhí)行版本管理及打包操作
maven-release-plugin 可用于構(gòu)建release版本項(xiàng)目,實(shí)現(xiàn)自動打tag、遞增版本號、分發(fā)release版本jar包至倉庫,接下來通過本文給大家介紹Springboot 使用maven release插件執(zhí)行版本管理及打包操作,需要的朋友可以參考下2022-03-03利用Spring Social輕松搞定微信授權(quán)登錄的方法示例
這篇文章主要介紹了利用Spring Social輕松搞定微信授權(quán)登錄的方法示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-12-12Java中easypoi的使用之導(dǎo)入校驗(yàn)
因工作需要,使用easypoi導(dǎo)入表格,并進(jìn)行校驗(yàn),將表格中有問題的地方,給出提示信息,以表格形式返回,下面這篇文章主要給大家介紹了關(guān)于Java中easypoi的使用之導(dǎo)入校驗(yàn)的相關(guān)資料,需要的朋友可以參考下2023-03-03