多線程間如何通信和共享資料,肯定好多小夥伴都不知道, 或者很久之前知道,現在已經忘卻,現在我和大家一起複習一下。
一、線程間通信
1、線程間通信:A執行完,B才執行
/**
* 線程間通信:A執行完,B才執行
*/
@Test
public void bWaitA(){
Thread A = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("A 開始工作!!");
System.out.println("A 結束工作!!");
}
});
Thread B = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("B 開始等待 A");
try {
A.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("B 開始工作!!");
System.out.println("B 結束工作!!");
}
});
B.start();
A.start();
}
2、A執行一部分,B執行完,A再執行剩下的部分
/**
* A執行一部分,B執行完,A再執行剩下的部分
* A 1, B 1, B 2, B 3, A 2, A 3
*
* 首先建立一個 A 和 B 共享的對象鎖 lock = new Object();
* 當 A 得到鎖後,先列印 1,然後調用 lock.wait() 方法,交出鎖的控制權,進入 wait 狀态;
* 對 B 而言,由于 A 最開始得到了鎖,導緻 B 無法執行;直到 A 調用 lock.wait() 釋放控制權後, B 才得到了鎖;
* B 在得到鎖後列印 1, 2, 3;然後調用 lock.notify() 方法,喚醒正在 wait 的 A;
* A 被喚醒後,繼續列印剩下的 2,3。
*/
@Test
public void aWaitB() {
Object lock = new Object();
Thread A = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("A 等待鎖");
synchronized (lock) {
System.out.println("A 獲得鎖");
System.out.println("A 1");
try {
System.out.println("A wait 放棄鎖控制權,等待再次擷取鎖控制權");
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("被喚醒,重新擷取鎖控制權");
System.out.println("A 2");
System.out.println("A 3");
}
}
});
Thread B = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("B 等待擷取鎖控制權");
synchronized (lock) {
System.out.println("B 擷取鎖控制權");
System.out.println("B 1");
System.out.println("B 2");
System.out.println("B 3");
System.out.println("B 執行完畢 調用 notiry 方法");
lock.notify();
}
}
});
A.start();
B.start();
}
3、四個線程 A B C D,其中 D 要等到 A B C 全執行完畢後才執行,而且 A B C 是同步運作的
/**
* 四個線程 A B C D,其中 D 要等到 A B C 全執行完畢後才執行,而且 A B C 是同步運作的
*
* CountdownLatch 來實作這類通信方式。它的基本用法是:
* 建立一個計數器,設定初始值,CountdownLatch countDownLatch = new CountDownLatch(2);
* 在 等待線程 裡調用 countDownLatch.await() 方法,進入等待狀态,直到計數值變成 0;
* 在 其他線程 裡,調用 countDownLatch.countDown() 方法,該方法會将計數值減小 1;
* 當 其他線程 的 countDown() 方法把計數值變成 0 時,等待線程 裡的 countDownLatch.await() 立即退出,繼續執行下面的代碼。
*
* 執行過程:
* 其實簡單點來說,CountDownLatch 就是一個倒計數器,我們把初始計數值設定為3,
* 當 D 運作時,先調用 countDownLatch.await() 檢查計數器值是否為 0,若不為 0 則保持等待狀态;
* 當A B C 各自運作完後都會利用countDownLatch.countDown(),将倒計數器減 1,
* 當三個都運作完後,計數器被減至 0;此時立即觸發 D 的 await() 運作結束,繼續向下執行。
* 是以,CountDownLatch 适用于一個線程去等待多個線程的情況。
*/
@Test
public void dWaitABC() {
int worker = 3;
CountDownLatch countDownLatch = new CountDownLatch(worker);
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("D 等待其他線程執行完畢");
try {
countDownLatch.await();
System.out.println("所有需要等待的線程都執行完畢,D開始工作");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
for (char threadName='A'; threadName <= 'C'; threadName++) {
final String threadNameStr = String.valueOf(threadName);
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(threadNameStr + " 正在工作");
System.out.println(threadNameStr + " 工作完成");
countDownLatch.countDown();
}
}).start();
}
}
4、個運動員各自準備,等到三個人都準備好後,再一起跑
/**
* 三個運動員各自準備,等到三個人都準備好後,再一起跑
*
* CyclicBarrier
* 為了實作線程間互相等待這種需求,我們可以利用 CyclicBarrier 資料結構,它的基本用法是:
* 先建立一個公共 CyclicBarrier 對象,設定 同時等待 的線程數,CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
* 這些線程同時開始自己做準備,自身準備完畢後,需要等待别人準備完畢,這時調用 cyclicBarrier.await(); 即可開始等待别人;
* 當指定的 同時等待 的線程數都調用了 cyclicBarrier.await();時,意味着這些線程都準備完畢好,然後這些線程才 同時繼續執行。
*/
@Test
public void runABCWhenAllReady() {
int runner = 3;
CyclicBarrier cyclicBarrier = new CyclicBarrier(runner);
final Random random = new Random();
for (char runnerName='A'; runnerName <= 'C'; runnerName++) {
final String runnerNameStr = String.valueOf(runnerName);
new Thread(new Runnable() {
@Override
public void run() {
long prepareTime = random.nextInt(1000) + 100;
System.out.println(runnerNameStr + " 線程正在做準備 " );
try {
Thread.sleep(prepareTime);
} catch (Exception e) {
e.printStackTrace();
}
try {
System.out.println(runnerNameStr + " 線程準備完畢等待其他線程準備好");
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println("所有線程都準備好了,"+runnerNameStr +" 線程開始工作");
}
}).start();
}
// 讓主線程休眠,讓裡面的線程執行完畢之前,主線程還存在
try {
Thread.sleep(60000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
二、線程資料共享
/**
* 線程間資料共享
* 不同的線程,都有一個屬性 ShareData ,并且線程的工作也封裝 ShareData中。
* 不同線程中的run方法執行的都是 ShareData 中指定的封裝的方法
*/
@Test
public void shareData(){
ShareData shareData = new ShareData();
for(int i=1;i<=2;i++){
new Thread(new MyIncreRunnable(shareData)).start();
new Thread(new MyDecreRunanble(shareData)).start();
}
}
/**
* 線程間資料共享
* 不同的線程,都有一個屬性 ShareData ,并且線程的工作也封裝 ShareData中。
* 不同線程中的run方法執行的都是 ShareData 中指定的封裝的方法
*/
@Test
public void shareData(){
ShareData shareData = new ShareData();
for(int i=1;i<=2;i++){
new Thread(new MyIncreRunnable(shareData)).start();
new Thread(new MyDecreRunanble(shareData)).start();
}
}
class MyIncreRunnable implements Runnable{
private ShareData shareData;
public MyIncreRunnable(ShareData shareData) {
this.shareData = shareData;
}
@Override
public void run() {
for (int i = 1;i<=10;i++){
shareData.increment();
}
}
}
class MyDecreRunanble implements Runnable{
private ShareData shareData;
public MyDecreRunanble(ShareData shareData) {
this.shareData = shareData;
}
@Override
public void run() {
for(int i=1;i<=10;i++){
shareData.decrement();
}
}
}
/**
* 共享資料,對共享資料的操作也在這個對象中完成
*/
class ShareData{
private int count = 0;
public synchronized void increment(){
count++;
System.out.println(Thread.currentThread().getName()+" inc "+count);
}
public synchronized void decrement(){
count--;
System.out.println(Thread.currentThread().getName()+" dec " +count);
}
}