JUC工具
JUC(Java Util Concurrent)是Java提供的一組併發工具和類,主要用於處理多執行緒程式設計中的常見問題。JUC包的主要目標是促進併發程式設計,並提供更簡潔和高效的方法來管理執行緒、鎖、執行任務等。
併發工具類
在JUC併發包裡提供了幾個非常有用的併發容器和併發工具類,供我們在多執行緒開發中進行使用。
CountDownLatch
CountDownLatch被稱之為倒計數器,CountDownLatch允許一個或多個執行緒等待其他執行緒完成操作以後,再執行當前執行緒。
CountDownLatch中count down是倒著數數的意思;CountDownLatch是透過一個計數器來實現的,每當一個執行緒完成了自己的任務後,可以呼叫countDown()方法讓計數器-1,當計數器到達0時,呼叫CountDownLatch的await()方法的執行緒阻塞狀態解除,繼續執行。
CountDownLatch的相關方法:
public CountDownLatch(int count) // 初始化一個指定計數器的CountDownLatch物件
public void await() throws InterruptedException // 讓當前執行緒等待,直到計數器值為0是解除堵塞狀態
public void countDown() // 計數器進行減1
比如我們在主執行緒需要開啟2個其他執行緒,當其他的執行緒執行完畢以後我們再去執行主執行緒,針對這個需求我們就可以使用CountDownLatch來進行實現。
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
public static void main(String[] args) {
// 建立一個 CountDownLatch,計數器的初始化值為2,表示需要等待兩個執行緒
CountDownLatch latch = new CountDownLatch(2);
// 建立兩個子執行緒
Thread thread1 = new Thread(() -> {
try {
// 模擬工作
System.out.println("Thread 1 is working...");
Thread.sleep(2000); // 模擬耗時操作
System.out.println("Thread 1 finished.");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 當前執行緒完成工作,計數器減1
latch.countDown();
}
});
Thread thread2 = new Thread(() -> {
try {
// 模擬工作
System.out.println("Thread 2 is working...");
Thread.sleep(3000); // 模擬耗時操作
System.out.println("Thread 2 finished.");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 當前執行緒完成工作,計數器減1
latch.countDown();
}
});
// 啟動執行緒
thread1.start();
thread2.start();
try {
// 主執行緒在這裡等待,直到計數器變為0
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 當兩個執行緒都完成時,繼續執行主執行緒的後續操作
System.out.println("Both threads are finished. Main thread is continuing...");
}
}
CyclicBarrier
CyclicBarrier的字面意思是可迴圈使用(Cyclic)的屏障(Barrier)。
它要做的事情是,讓一組執行緒到達一個屏障(也可以叫同步點)時被阻塞,直到最後一個執行緒到達屏障時,屏障才會開門,所有被屏障攔截的執行緒才會繼續執行。
CyclicBarrier的相關方法
// 用於線上程到達屏障時,優先執行barrierAction,方便處理更復雜的業務場景
public CyclicBarrier(int parties, Runnable barrierAction)
// 每個執行緒呼叫await方法告訴CyclicBarrier我已經到達了屏障,然後當前執行緒被阻塞
public int await()
用於讓一組執行緒互相等待,直到達到某個公共的屏障點。在達到這個屏障點之前,所有的執行緒都必須呼叫
await()
方法。一旦所有執行緒都到達了這個屏障,所有執行緒會被釋放並繼續執行。
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierExample {
public static void main(String[] args) {
// 建立一個 CyclicBarrier,設定等待的執行緒數量為3
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
// 這個任務會在所有執行緒到達屏障時執行
System.out.println("All threads have reached the barrier. Let's proceed.");
});
// 建立3個執行緒
Runnable task = () -> {
try {
System.out.println(Thread.currentThread().getName() + " is working...");
// 模擬工作
Thread.sleep((long) (Math.random() * 3000));
System.out.println(Thread.currentThread().getName() + " is ready to meet at the barrier.");
// 等待其他執行緒到達屏障
barrier.await();
// 到達屏障後繼續執行
System.out.println(Thread.currentThread().getName() + " has crossed the barrier.");
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
};
// 啟動3個執行緒
Thread thread1 = new Thread(task, "Thread 1");
Thread thread2 = new Thread(task, "Thread 2");
Thread thread3 = new Thread(task, "Thread 3");
thread1.start();
thread2.start();
thread3.start();
}
}
Semaphore
Semaphore字面意思是訊號量的意思,它的作用是控制訪問特定資源的執行緒數目。
Semaphore的常用方法
public Semaphore(int permits) permits 表示許可執行緒的數量
public void acquire() throws InterruptedException 表示獲取許可
public void release() 表示釋放許可
比如給定一個資源數目有限的資源池,假設資源數目為N,每一個執行緒均可獲取一個資源,但是當資源分配完畢時,後來執行緒需要阻塞等待,直到前面已持有資源的執行緒釋放資源之後才能繼續。
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
// 建立一個 Semaphore,允許最多2個執行緒同時訪問
private static final Semaphore semaphore = new Semaphore(2);
public static void main(String[] args) {
// 建立5個執行緒
for (int i = 0; i < 5; i++) {
new Thread(new Worker(i)).start();
}
}
static class Worker implements Runnable {
private final int threadNumber;
public Worker(int threadNumber) {
this.threadNumber = threadNumber;
}
@Override
public void run() {
try {
// 獲取許可證
System.out.println("Thread " + threadNumber + " is trying to acquire a permit.");
semaphore.acquire();
System.out.println("Thread " + threadNumber + " has acquired a permit.");
// 模擬工作
Thread.sleep(2000);
// 釋放許可證
System.out.println("Thread " + threadNumber + " is releasing the permit.");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 釋放許可證,即使發生異常也要確保釋放
semaphore.release();
System.out.println("Thread " + threadNumber + " has released the permit.");
}
}
}
}
JMM(Java記憶體模型)
概述:JMM(Java Memory Model,Java記憶體模型),是java虛擬機器規範中所定義的一種記憶體模型。
JMM描述了Java程式中各種變數(執行緒共享變數)的訪問規則,以及在JVM中將變數儲存到記憶體和從記憶體中讀取變數這樣的底層細節。
特點:
1、所有的執行緒共享變數都儲存於主記憶體
(計算機的RAM)。
2、每個執行緒擁有自己的工作記憶體
,保留了被執行緒使用的變數
的工作副本
。
3、執行緒對變數的所有的操作(讀/寫)都必須在自己的工作記憶體
中完成,而不能直接讀寫主記憶體
中的變數。
4、不同執行緒之間也不能直接訪問對方工作記憶體中的變數,執行緒間變數的值的傳遞需要透過主記憶體完成。
volatile關鍵字
volatile
是 Java 中的一個關鍵字,用於變數的宣告。它的主要作用是保證多執行緒環境中的可見性和防止指令重排序。具體來說,
volatile
關鍵字能確保被它修飾的變數在多個執行緒之間的可見性,以及在讀取該變數時不會被最佳化掉,從而提供了一種輕量級的同步方式。
可見性
在沒有使用 volatile
的情況下,執行緒可能會在其本地快取中儲存變數的值,而不是從主記憶體中讀取。當一個執行緒修改了一個共享變數的值時,其他執行緒可能無法立即看到這個修改。這會導致執行緒間的狀態不一致。
使用 volatile
修飾的變數,Java 保證了以下幾點:
當一個執行緒修改了被 volatile
修飾的變數的值時,其他執行緒能夠立即看到這個變化。這是透過在寫入時將新的值重新整理到主記憶體中,並在讀取時從主記憶體中獲取最新的值來實現的。
防止指令重排序
Java編譯器和執行時可以對程式進行最佳化,包括指令重排序,以提高執行效率。使用 volatile
可以防止這些最佳化,確保對 volatile
變數的寫入和後續對該變數的讀取不會被重排序。
注意事項
不具備原子性:volatile
僅僅保證可見性和防止重排序,但不保證對變數的操作是原子的。例如,對於遞增操作(如 count++
),要用鎖或其他方式保證原子性。
適用場景:可以用 volatile
來標記那些只需要確保可見性而不是複雜操作的情況下,例如標誌位、狀態變數等。對於需要複合操作的多執行緒場景,應該考慮使用 synchronized
、Lock
等機制。
public class VolatileThread extends Thread {
// 定義成員變數
private volatile boolean flag = false ;
public boolean isFlag() { return flag;}
@Override
public void run() {
// 執行緒休眠1秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 將flag的值更改為true
this.flag = true ;
System.out.println("flag=" + flag);
}
}
控制檯輸出結果
flag=true
執行了======
執行了======
執行了======
....
併發程式設計三特性
可見性
可見性是指當多個執行緒訪問同一個變數時,一個執行緒修改了這個變數的值,其他執行緒能夠立即看到修改的值。
volatile關鍵字修飾的共享變數是可以保證可見性的。
原子性
概述:所謂的原子性是指在一次操作或者多次操作中,要麼所有的操作全部都得到了執行並且不會受到任何因素的干擾而中斷,要麼所有的操作都不執行,多個操作是一個不可以分割的整體。volatile不保證對變數的操作是原子性的
問題處理
1、加鎖 synchronized、lock
2、使用原子類 ==>AtomicInterger
有序性
有序性:程式碼編寫順序和程式碼執行順序一致。
計算機在執行程式時,為了提高效能,編譯器和處理器有時會對指令重排。
原子類與CAS演算法
原子類(AtomicInteger)
AtomicInteger原理: 自旋鎖 + CAS演算法
概述:java 從 JDK1.5 開始提供了 java.util.concurrent.atomic包(簡稱Atomic包)。
這個包中的
原子操作類
提供了一種執行緒安全
地更新一個變數的方式,且簡單高效。
因為變數的型別有很多種,Atomic包裡一共提供了13個類,屬於4種型別的原子更新方式,分別是原子更新基本型別
、原子更新陣列
、原子更新引用
和原子更新屬性(欄位)。
使用原子的方式更新
基本型別
,使用原子的方式更新基本型別Atomic包提供了以下3個類:AtomicBoolean: 原子更新布林型別
AtomicInteger: 原子更新整型
AtomicLong: 原子更新長整型
AtomicInteger的常用方法:
AtomicInteger
是 Java 中用於處理整數的一個原子類,它提供了一種無鎖的方式來進行整數值的更新,使得多執行緒環境下對整數的安全操作變得更為簡單。
public AtomicInteger(): 初始化一個預設值為0的原子型Integer
public AtomicInteger(int initialValue): 初始化一個指定值的原子型Integer
int get(): 獲取當前值
int getAndIncrement(): 以原子方式將當前值加1,注意,這裡返回的是自增前的值。 i++
int incrementAndGet(): 以原子方式將當前值加1,注意,這裡返回的是自增後的值。 ++i
int addAndGet(int data): 以原子方式將輸入數值與例項中值(AtomicInteger裡的value)相加,並返回結果。
int getAndSet(int value): 以原子方式設定為newValue的值,並返回舊值。
基本使用
public class AtomicIntegerDemo {
// 原子型Integer
public static void main(String[] args) {
// 構造方法
// public AtomicInteger():初始化一個預設值為0的原子型Integer
// AtomicInteger atomicInteger = new AtomicInteger() ;
// System.out.println(atomicInteger);
// public AtomicInteger(int initialValue): 初始化一個指定值的原子型Integer
AtomicInteger atomicInteger = new AtomicInteger(5) ;
System.out.println(atomicInteger);
// 獲取值
System.out.println(atomicInteger.get());
// 以原子方式將當前值加1,這裡返回的是自增前的值
System.out.println(atomicInteger.getAndIncrement());
System.out.println(atomicInteger.get());
// 以原子方式將當前值加1,這裡返回的是自增後的值
System.out.println(atomicInteger.incrementAndGet());
// 以原子方式將輸入的數值與例項中的值(AtomicInteger裡的value)相加,並返回結果
System.out.println(atomicInteger.addAndGet(8));
// 以原子方式設定為newValue的值,並返回舊值
System.out.println(atomicInteger.getAndSet(20));
System.out.println(atomicInteger.get());
}
}
AtomicInteger原始碼分析
public final class AtomicInteger extends Number implements Serializable {
private static final long serialVersionUID = 6214790243416808450L;
private final AtomicIntegerFieldUpdater updater;
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
private volatile int value;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
public AtomicInteger() {
this.value = 0;
}
public AtomicInteger(int initialValue) {
this.value = initialValue;
}
// 省略其他方法
}
- volatile 關鍵字:
value
欄位被宣告為volatile
,這確保了多個執行緒對value
的讀寫操作的可見性。 - Unsafe 類:使用內部類
Unsafe
來進行低階的記憶體操作,提供 CAS 操作。
CAS 方法:compareAndSet
javapublic final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
- compareAndSwapInt:這個方法的邏輯是:如果當前
value
等於expect
,就將value
更新為update
。這是一種原子操作,保證了在併發情況下的安全性。
增加和減少的方法:incrementAndGet
和 decrementAndGet
javapublic final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
public final int decrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, -1) - 1;
}
incrementAndGet
:透過getAndAddInt
增加 1,並返回更新後的值。decrementAndGet
:減少 1,並返回更新後的值。
UnSafe類
Unsafe類:jdk提供的一個不安全的類,平時開發時,肯定不會使用這個類。
該類用於直接去操作主記憶體。 (某個地址位置的變數)
該類中提供了CAS方法,用於操作主記憶體中的共享變數。
public final class Unsafe {
// Unsafe類中的getAndAddInt方法
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
// do...while就是自旋操作,當CAS成功以後,迴圈結束
do {
// 獲取AtomicInteger類中所封裝的int型別的值,就相當於舊的預期值A
v = getIntVolatile(o, offset);
// 呼叫本類的weakCompareAndSetInt方法實現比較在交換; o: AtomicInteger物件, v: 相當於舊的預期值A, v + delta:新值B
} while (!weakCompareAndSetInt(o, offset, v, v + delta));
return v;
}
// Unsafe類中的weakCompareAndSetInt方法
public final boolean weakCompareAndSetInt(Object o, long offset, int expected, int x) {
return compareAndSetInt(o, offset, expected, x);
}
// 本地方法,呼叫CPU指令實現CAS
public final native boolean compareAndSetInt(Object o, long offset, int expected, int x);
}
CAS演算法
CAS的全稱是: Compare And Swap(比較再交換),是一種對記憶體中的共享資料進行操作的一種特殊指令。
CAS可以將 read-modify-write 轉換為
原子
操作,這個原子操作直接由CPU保證。CAS有3個運算元(引數):記憶體值V(記憶體中的一個變數),舊的預期值E,要修改的新值N。
- V:變數的值
- E:期望值(Expected Value)
- N:新值(New Value)
CAS的執行過程如下:
- 演算法會檢查變數V的當前值是否等於期望值E。
- 如果相等,CAS操作將變數V的值更新為新值N。
- 如果不相等,變數V的值不變,並且操作返回V的當前值。
舉例說明:
- 在記憶體值V,儲存著值為10的變數。
- 此時執行緒1想要把變數的值增加1。對執行緒1來說,舊的預期值 A = 10 ,要修改的新值 B = 11。
- 線上程1要提交更新之前,另一個執行緒2搶先一步,把記憶體值V中的變數值率先更新成了11。
- 執行緒1開始提交更新,首先進行A和記憶體值V的實際值比較(Compare),發現A不等於V的值,提交失敗。
- 執行緒1重新獲取記憶體值V作為當前A的值,並重新計算想要修改的新值。此時對執行緒1來說,A = 11,B = 12。這個重新嘗試的過程被稱為自旋。
- 這一次比較幸運,沒有其他執行緒改變V的值。執行緒1進行Compare,發現A和V的值是相等的。
- 執行緒1進行SWAP,把記憶體V的值替換為B,也就是12。
舉例說明:這好比春節的時候搶火車票,下手快的會搶先買到票,而下手慢的可以再次嘗試,直到買到票。
CAS缺點
1、在併發量比較高的情況下,如果反覆嘗試更新某個變數,卻又一直更新不成功,會給CPU帶來較大的壓力。
2、ABA問題:當變數從A修改為B再修改回A時,變數值等於期望值A,但是無法判斷是否修改,CAS操作在ABA修改後依然成功。
3、CAS機制只保證一個變數的原子性操作,而不能保證整個程式碼塊的原子性。
AQS(抽象佇列同步器)-瞭解
在Java的java.util.concurrent
(簡稱JUC)包中,AbstractQueuedSynchronizer
(AQS,抽象佇列同步器)是一個核心的基礎類,它為依賴於先進先出(FIFO)等待佇列的阻塞鎖和相關的同步器(如Semaphore
、CountDownLatch
、ReentrantLock
、ReentrantReadWriteLock
等)提供了一個框架。
AQS是一個基於狀態(通常使用一個volatile的int型別的變數state
表示)的同步器,它使用了一個內部的FIFO佇列來完成等待執行緒的管理。這個佇列被稱為CLH佇列,用於在獲取不到資源的執行緒之間進行排隊。
作用:
- 同步狀態管理:AQS使用一個volatile的int型別的變數
state
來維護同步狀態。state
為0表示沒有執行緒佔用資源,非0表示有執行緒佔用資源。 - 佇列管理:AQS內部維護了一個等待佇列,當執行緒獲取不到資源時,會加入到這個佇列中進行等待,直到被喚醒或超時。
- 獨佔模式和共享模式:AQS支援兩種模式的資源訪問控制:獨佔模式和共享模式。獨佔模式意味著只有一個執行緒能訪問執行;共享模式允許多個執行緒同時訪問執行。
AQS同步佇列的head節點是一個空節點,沒有記錄執行緒node.thread=null,其後繼節點才是實質性的有執行緒的節點。
AQS兩種模式
獨佔模型
典型的實現類ReentrantLock==》state初始值0,加鎖成功,則將state+1
獨佔模式下時,其他執行緒試圖獲取該鎖將無法取得成功,只有一個執行緒能執行,如ReentrantLock採用獨佔模式。
執行緒獲取鎖(非公平)的流程:
- state初始值=0,並且被volatile關鍵字進行修飾。加鎖成功就是將state由0改成1,如果是鎖的重入則繼續+1…
- 首先透過cas的方式將state由期望值0,改成1,如果成功,則說明加鎖成功,並且exclusiveOwnerThread屬性記錄當前執行緒加鎖成功的執行緒。
- 如果上一步直接加鎖失敗,說明state現在不等於0,接下來判斷當前執行緒是否是正在持有鎖的執行緒,如果是則說明當前執行緒在進行鎖的重入,此時將state+1即可。
- 如果上一步不是鎖的重入,再一次直接嘗試獲取鎖。
- 如果上一步再次獲取鎖又失敗了,此時當前執行緒就需要被封裝成一個Node物件加入到雙向連結串列的末尾。
執行緒獲取鎖(公平)的流程:
- 當鎖空閒時,也就是state=0,先判斷等待佇列中是否存在等待鎖的執行緒,如果佇列中沒有現成正在排隊,此時當前執行緒就不需要去加入到佇列中,而是可以直接去加鎖。
- 如果上一步判斷state不為空,說明鎖並非空閒(有執行緒正在持有鎖),接下來判斷正在持有鎖的執行緒 是 否就是當前執行緒,如果是,則說明當前執行緒在進行鎖的重入,state+1即可
- 如果上一步不是鎖的重入,會先再一次判斷:如果此時鎖空閒並且等待佇列為空,可以直接嘗試cas去加鎖。
- )如果上一步鎖並非空閒或cas失敗,當前執行緒只能封裝成Node物件加入到等待佇列。
執行緒釋放鎖的流程(公平非公平一至):
- 判斷當前執行緒是否是正在持有鎖的執行緒,如果不是,則丟擲異常。
- 獲取state並且-1(更新state),如果state=0,則將exclusiveOwnerThread屬性設定為null, 當state為0時,說明當前鎖被徹底釋放。
- 當前執行緒如果是正在持有鎖的執行緒,將stat-1之後,如果不是0,說明當前執行緒之前獲取鎖的過程中有進行鎖的重入。
- 如果釋放鎖之後state-1等於0了,此時將頭結點的下一個節點對應的waiter屬性(執行緒物件)進行喚醒,被喚醒後,當前節點就可以去搶佔鎖。
共享模型
典型的實現類Semaphore==》state初始值n(自定義),加鎖成功,則將state-1
執行緒獲取鎖(非公平)的流程:
- 獲取state值,並且-1,得到的結果表示共享鎖剩餘的資源數
- 如果-1後,此時共享鎖的剩餘資源<0, 說明共享鎖的多個資源都被佔用了,接下來將當前執行緒封裝到一個SharedNode節點,該節點加入到等待佇列中。
- 如果上一步state-1之後>0,透過cas的方式去修改state,如果cas失敗,則自旋,重新再來一遍(重新獲取state, 重新-1,重新cas)
- 當多次cas操作成功後,一定返回一個大於等於0的整數。加鎖過程則結束。
執行緒獲取鎖(公平)的流程:
判斷佇列為空,則cs+自旋嘗試加鎖,每次自旋時都會重新判斷佇列是否為空,如果佇列不為空,當前執行緒就不能去加鎖,而是加入到等待佇列中。
執行緒釋放鎖的流程(公平非公平一至):
- Sempore中釋放資源就是要務tate+1
- 獲取state值,該值+1,透過cas的方式去修改state,如果cas失敗,則自旋重試
非同步編排
問題:查詢商品詳情頁的邏輯非常複雜,資料的獲取都需要遠端呼叫,必然需要花費更多的時間。
假如商品詳情頁的每個查詢,需要如下標註的時間才能完成
- 獲取sku的基本資訊 1s
. 獲取商品資訊 1.5s
. 商品最新價格 0.5s
那麼,使用者需要3s後才能看到商品詳情頁的內容。很顯然是不能接受的。如果有多個執行緒同時完成這4步操作,也許只需要1.5s即可完成響應。
CompletableFuture
可以使原本序列執行的程式碼,變為並行執行,提高程式碼執行速度。
CompletableFuture
CompletableFuture
是 Java8 引入的一個非常強大的工具類,屬於 java.util.concurrent
包,它支援非同步程式設計和並行處理,能夠更方便地處理非同步計算結果。透過 CompletableFuture
,你可以構建一系列非同步任務,並在任務完成後執行特定的操作,或處理任務的結果。
常用方法
supplyAsync
:supplyAsync是建立帶有返回值的非同步任務。
runAsync
:runAsync是建立沒有返回值的非同步任務。
thenApply和thenApplyAsync
:表示某個任務執行完成後執行的動作,即回撥方法,會將該任務的執行結果即方法返回值作為入參傳遞到回撥方法中,帶有返回值。
thenApply和thenApplyAsync區別在於,使用thenApply方法時子任務與父任務使用的是同一個執行緒,而thenApplyAsync在子任務中是另起一個執行緒執行任務。
thenAccept和thenAcceptAsync
:表示某個任務執行完成後執行的動作,即回撥方法,會將該任務的執行結果即方法返回值作為入參傳遞到回撥方法中,無返回值。
thenAccep和thenAccepAsync區別在於,使用thenAccep方法時子任務與父任務使用的是同一個執行緒,而thenAccepAsync在子任務中可能是另起一個執行緒執行任務。
thenRun和thenRunAsync
:表示某個任務執行完成後執行的動作,即回撥方法,無入參,無返回值。
allOf / anyOf
allOf:CompletableFuture是多個任務都執行完成後才會執行,只有有一個任務執行異常,則返回的CompletableFuture執行get方法時會丟擲異常,如果都是正常執行,則get返回null。
anyOf :CompletableFuture是多個任務只要有一個任務執行完成,則返回的CompletableFuture執行get方法時會丟擲異常,如果都是正常執行,則get返回執行完成任務的結果。
1、ThreadPoolConfig
全域性自定義執行緒池配置
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@Configuration
public class ThreadPoolConfig {
@Bean
public ThreadPoolExecutor threadPoolExecutor() {
//當前系統可用的處理器數量
int processorsCount = Runtime.getRuntime().availableProcessors();
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
processorsCount * 2,
processorsCount * 2,
0,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(200),
Executors.defaultThreadFactory(),
//new ThreadPoolExecutor.CallerRunsPolicy()
//自定義拒絕策略
(runnable, executor) -> {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
}
//再次將拒絕任務提交給執行緒池執行
executor.submit(runnable);
}
);
//執行緒池建立,核心執行緒同時建立
//threadPoolExecutor.prestartCoreThread();
threadPoolExecutor.prestartAllCoreThreads();
return threadPoolExecutor;
}
}
2、使用CompletableFuture
@Autowired
private ThreadPoolExecutor threadPoolExecutor;
public void test() {
ItemVo itemVo = new ItemVo();
//獲取sku資訊
CompletableFuture<ProductSku> skuCompletableFuture = CompletableFuture.supplyAsync(() -> {
//程式碼內容1
return ProductSku;
}, threadPoolExecutor);
//獲取商品資訊
CompletableFuture<Void> productComCompletableFuture = skuCompletableFuture.thenAcceptAsync(productSku -> { //程式碼內容2
}, threadPoolExecutor);
//獲取商品最新價格
CompletableFuture<Void> skuPriceCompletableFuture = CompletableFuture.runAsync(() -> {
//程式碼內容3
}, threadPoolExecutor);
//獲取商品詳情
CompletableFuture<Void> productDetailsComCompletableFuture = skuCompletableFuture.thenAcceptAsync(productSku -> {
//程式碼內容4
}, threadPoolExecutor);
//獲取商品規格對應商品skuId資訊
CompletableFuture<Void> skuSpecValueComCompletableFuture = skuCompletableFuture.thenAcceptAsync(productSku -> {
//程式碼內容5
}, threadPoolExecutor);
//獲取商品庫存資訊
CompletableFuture<Void> skuStockVoComCompletableFuture = CompletableFuture.runAsync(() -> {
//程式碼內容6
}, threadPoolExecutor);
//x.組合以上七個非同步任務
//多個任務都執行完成後才會執行
CompletableFuture.allOf(
skuCompletableFuture,
productComCompletableFuture,
skuPriceCompletableFuture,
productDetailsComCompletableFuture,
skuSpecValueComCompletableFuture,
skuStockVoComCompletableFuture
).join();
return itemVo;
}