Java多執行緒/併發13、保持執行緒間的資料獨立: Collections.synchronizedMap應用
現在流行分散式計算,分散式計算就是先分開計算,然後統一彙總。比如這道題目: 。先別跑,小學題很簡單的。
解釋一下,左邊那一砣是計算從1加到n的值(求和),右邊是n乘到1的值(階乘),再把兩個值相加得到最終結果。假設求和運算需要5秒鐘,階乘運算需要7秒鐘,相加的運算需要1秒,那麼總耗時是13秒。而在分散式計算中,由兩臺機器同時進行計算,得到求和及階乘的兩個結果只需要7秒,再相加需要1秒,總耗時8秒。
下面,我們通過兩個執行緒來模擬這個過程。
首先定義計算類,這三個類作為外部類,存在於Main()函式所在類的外面。程式碼很簡單,看起來很長,是因為加了幾組try..catch對。
/* 定義計算器,讓計算由統一的類來管理 */
class Calculator {
public static int result = 0;
void getFactorial(int n) throws Exception {
/* 模擬階乘運算需要7秒 */
try {
Thread.sleep(7000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
/* 進行階乘運算 */
result = Factorial.GetResult(n);
} catch (Exception e) {
throw new Exception(e.getMessage());
}
System.out.println("執行緒" + Thread.currentThread().getName()
+ "輸出結果:" + String.valueOf(Calculator.result));
}
void getAccu(int n) throws Exception {
/* 模擬求和運算需要5秒 */
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
result = Accu.GetResult(n);/* 進行求和運算 */
} catch (Exception e) {
throw new Exception(e.getMessage());
}
System.out.println("執行緒" + Thread.currentThread().getName()
+ "輸出結果:" + String.valueOf(Calculator.result));
}
}
/* 定義階乘處理類Factorial 這裡是模擬實現,實際開發中需要用大數處理類BigInteger才行*/
class Factorial {
public static int GetResult(int n) throws Exception {
if (n < 0 || n > 10) {
throw new Exception("error:玩玩而已,輸入不能小於零,也不能大於10");
}
if (n <= 2) {
return n;
}
return n * GetResult(n - 1);
}
}
/* 定義求和處理類Accu */
class Accu {
public static int GetResult(int n) throws Exception {
if (n < 0 || n >= 1000) {
throw new Exception("error:玩玩而已,輸入不能小於零,也不能大於1000");
}
if (n <= 1) {
return n;
}
return n + GetResult(n - 1);
}
}
接下來,在Main()主執行緒中開始呼叫類來計算。
public class ExecuteDemo {
public static void main(String[] args) {
final Calculator calculator = new Calculator();
new Thread(new Runnable() {
public void run() {
try {
calculator.getFactorial(10);
} catch (Exception e) {
System.err.println(e.getMessage());
}
/*模擬很多執行緒併發執行時造成的競爭*/
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Factorial結果:" + String.valueOf(Calculator.result));
}
}).start();
new Thread(new Runnable() {
public void run() {
try {
calculator.getAccu(10);
} catch (Exception e) {
System.err.println(e.getMessage());
}
/*模擬很多執行緒併發執行時造成的競爭*/
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Accu結果:" + String.valueOf(Calculator.result));
}
}).start();
}
}
輸出結果:
執行緒Thread-1輸出結果:55
執行緒Thread-0輸出結果:3628800
Accu結果:3628800
Factorial結果:3628800
執行緒呼叫方法運算的值是正確的,但是在取值卻存在錯誤,Thread-1的計算結果被覆蓋了。看來寫出的這個計算類並不是執行緒安全的,問題就出在存放計算結果的變數static int result上。因為靜態成員(static member)作為公共變數,就是放在共享記憶體區域的(方法區)。
在Calculator類中,我們用一個static int result來儲存類中成員方法計算的結果,但多個執行緒併發呼叫方法時,都會搶佔result向其寫入資料,最終只會保留最後一個執行緒計算的值。我們需要讓每個執行緒都保持各自的計算結果,自然想到了HashMap來儲存。java提供了一個執行緒安全的HashMap包裝: Collections.synchronizedMap。
我們來改動一下Calculator類,程式碼如下:
class Calculator {
public static Map<Thread, Integer> DataContainer = Collections.synchronizedMap(new HashMap<Thread, Integer>());
void getFactorial(int n) throws Exception {
/* 模擬階乘運算需要7秒 */
try {
Thread.sleep(7000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int result=0;
try {
result = Factorial.GetResult(n);/* 進行階乘運算 */
DataContainer.put(Thread.currentThread(), result);
} catch (Exception e) {
throw new Exception(e.getMessage());
}
System.out.println("執行緒" + Thread.currentThread().getName()
+ "輸出結果:" + String.valueOf(result));
}
void getAccu(int n) throws Exception {
/* 模擬求和運算需要5秒 */
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int result=0;
try {
result = Accu.GetResult(n);/* 進行求和運算 */
DataContainer.put(Thread.currentThread(), result);
} catch (Exception e) {
throw new Exception(e.getMessage());
}
System.out.println("執行緒" + Thread.currentThread().getName()
+ "輸出結果:" + String.valueOf(result));
}
}
Main()函式的輸出調整:
public static void main(String[] args) {
final Calculator calculator = new Calculator();
new Thread(new Runnable() {
public void run() {
try {
calculator.getFactorial(10);
} catch (Exception e) {
System.err.println(e.getMessage());
}
/* 模擬耗時操作中多個執行緒執行時搶佔造成的競爭 */
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Factorial結果:"
+ String.valueOf(Calculator.DataContainer.get(Thread
.currentThread())));
}
}).start();
new Thread(new Runnable() {
public void run() {
try {
calculator.getAccu(10);
} catch (Exception e) {
System.err.println(e.getMessage());
}
/* 模擬耗時操作中多個執行緒執行時搶佔造成的競 */
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Accu結果:"
+ String.valueOf(Calculator.DataContainer.get(Thread
.currentThread())));
}
}).start();
}
兩個執行緒互不影響,成功輸出:
執行緒Thread-1輸出結果:55
執行緒Thread-0輸出結果:3628800
Accu結果:55
Factorial結果:3628800
最後,計算兩個結果的和
在Main()函式兩個執行緒呼叫的後面,加上下面這段,看看輸出的結果:
Iterator<Map.Entry<Thread, Integer>> it=Calculator.DataContainer.entrySet().iterator();
int sum=0;
while(it.hasNext()){
Map.Entry<Thread, Integer> entry = (Map.Entry<Thread, Integer>) it.next();
Integer value = (Integer)entry.getValue();
sum+=value;
}
System.out.println("最終計算結果"+String.valueOf(sum));
執行輸出:
最終計算結果0
執行緒Thread-1輸出結果:55
執行緒Thread-0輸出結果:3628800
Accu結果:55
Factorial結果:3628800
我K,還是不對。
原來,進入Main()函式後,開啟兩個子執行緒計算後,主執行緒並沒有停止,繼續往下執行。由於兩個子執行緒是耗時操作,而主執行緒如小李飛刀般快的速度往下執行,這時侯Calculator.DataContainer還是空的,結果當然為0。
我們應該阻塞主執行緒,直到Calculator.DataContainer有兩個計算結果值後,才允許計算最後的相加結果。
在Iterator it=Calculator.DataContainer.entrySet().iterator();
這句的前面加上:
while(Calculator.DataContainer.size()<2){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
還有一種方法,呼叫兩個執行緒的join()方法來實現呼叫執行緒(主執行緒)阻塞,等兩個執行緒執行完畢再繼續執行主執行緒,不過這要求兩個執行緒不能用匿名方式實現,這裡就不改程式了。具體join用法參考《Thread.Join()讓呼叫執行緒等待子執行緒 》
執行輸出:
執行緒Thread-1輸出結果:55
執行緒Thread-0輸出結果:3628800
最終計算結果3628855
Accu結果:55
Factorial結果:3628800
相關文章
- Java多執行緒/併發14、保持執行緒間的資料獨立:ConcurrentHashMap應用Java執行緒HashMap
- Java多執行緒/併發05、synchronized應用例項:執行緒間操作共享資料Java執行緒synchronized
- JAVA多執行緒併發Java執行緒
- java多執行緒與併發 - 執行緒池詳解Java執行緒
- Java多執行緒/併發08、中斷執行緒 interrupt()Java執行緒
- java 多執行緒 併發 面試Java執行緒面試
- Java 併發和多執行緒(一) Java併發性和多執行緒介紹[轉]Java執行緒
- 多執行緒併發篇——如何停止執行緒執行緒
- java多執行緒5:執行緒間的通訊Java執行緒
- Java多執行緒——執行緒Java執行緒
- Java多執行緒/併發12、多執行緒訪問static變數Java執行緒變數
- Java高併發與多執行緒(二)-----執行緒的實現方式Java執行緒
- Java多執行緒/併發06、執行緒鎖Lock與ReadWriteLockJava執行緒
- Java併發和多執行緒:序Java執行緒
- Java併發 之 執行緒池系列 (1) 讓多執行緒不再坑爹的執行緒池Java執行緒
- 多執行緒與高併發(二)執行緒安全執行緒
- 併發與多執行緒之執行緒安全篇執行緒
- Java多執行緒/併發07、Thread.Join()讓呼叫執行緒等待子執行緒Java執行緒thread
- 多執行緒程式設計,處理多執行緒的併發問題(執行緒池)執行緒程式設計
- 【java 多執行緒】多執行緒併發同步問題及解決方法Java執行緒
- Java多執行緒-執行緒中止Java執行緒
- Java多執行緒——執行緒池Java執行緒
- 多執行緒與高併發(一)多執行緒入門執行緒
- 淺談執行緒池(中):獨立執行緒池的作用及IO執行緒池執行緒
- Java 併發:執行緒、執行緒池和執行器全面教程Java執行緒
- iOS多執行緒安全-13種執行緒鎖?iOS執行緒
- Java多執行緒/併發11、執行緒同步通訊:notify、waitJava執行緒AI
- Java併發(四)----執行緒執行原理Java執行緒
- 多執行緒應用執行緒
- pytest(13)-多執行緒、多程式執行用例執行緒
- Java多執行緒——獲取多個執行緒任務執行完的時間Java執行緒
- java多執行緒與併發 - 併發工具類Java執行緒
- Java多執行緒-執行緒池的使用Java執行緒
- 【Java多執行緒】執行緒安全的集合Java執行緒
- JAVA多執行緒和併發基礎Java執行緒
- Java併發/多執行緒-CAS原理分析Java執行緒
- 【多執行緒與高併發】Java守護執行緒是什麼?什麼是Java的守護執行緒?執行緒Java
- java併發與執行緒Java執行緒