JAVA多線程——(三)多線程編程
文章目錄
【一】Future
Future應用場景
- 在併發編程中,我們經常用到非阻塞的模型,在之前的多線程的三種實現中,不管是繼承thread類還是實現runnable接口,都無法保證獲取到之前的執行結果。通過實現Callback接口,並用Future可以來接收多線程的執行結果。
- Future表示一個可能還沒有完成的異步任務的結果,針對這個結果可以添加Callback以便在任務執行成功或失敗後作出相應的操作。
FutureTask的類圖結構
public class Main {
public static Integer consumerFunction(){
System.out.println("這是消費者:");
return 1;
}
public static Integer producerFunction(){
System.out.println("這是生產者:");
return 2;
}
public static void main(String args[]) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
//生產者
FutureTask<Integer> consumer = new FutureTask<Integer>(()->consumerFunction());
//消費者
FutureTask<Integer> producer = new FutureTask<Integer>(()->producerFunction());
//提交任務
executorService.submit(consumer);
executorService.submit(producer);
int resultProducer = producer.get();
int resultConsumer = consumer.get();
System.out.println("resultConsumer:"+resultConsumer+" resultProducer:"+resultProducer);
}
}
【二】CompletableFuture
CompletableFuture 是一個 Future
CompletableFuture 提供了 join() 方法,它的功能和 get() 方法是一樣的,都是阻塞獲取值,它們的區別在於 join() 拋出的是 unchecked Exception。
上面的代碼確實沒什麼用,下面介紹幾個 static 方法,它們使用任務來實例化一個 CompletableFuture 實例。
CompletableFuture.runAsync(Runnable runnable);
CompletableFuture.runAsync(Runnable runnable, Executor executor);
CompletableFuture.supplyAsync(Supplier<U> supplier);
CompletableFuture.supplyAsync(Supplier<U> supplier, Executor executor)
- runAsync 方法接收的是 Runnable 的實例,意味着它沒有返回值
- supplyAsync 方法對應的是有返回值的情況
這兩個方法的帶 executor 的變種,表示讓任務在指定的線程池中執行,不指定的話,通常任務是在 ForkJoinPool.commonPool() 線程池中執行的。
2.1 任務之間執行順序(串行):
我們先來看執行兩個任務的情況,首先執行任務 A,然後將任務 A 的結果傳遞給任務 B。
其實這裏有很多種情況:
任務 A 是否有返回值
任務 B 是否需要任務 A 的返回值
任務 B 是否有返回值,等等。
有個明確的就是,肯定是任務 A 執行完後再執行任務 B。
我們用下面的 6 行代碼來說:
CompletableFuture.runAsync(() -> {}).thenRun(() -> {});
CompletableFuture.runAsync(() -> {}).thenAccept(resultA -> {});
CompletableFuture.runAsync(() -> {}).thenApply(resultA -> "resultB");
//任務 A 執行完執行 B,並且 B 不需要 A 的結果
CompletableFuture.supplyAsync(() -> "resultA").thenRun(() -> {});
//任務 A 執行完執行 B,B 需要 A 的結果,但是任務 B 不返回值
CompletableFuture.supplyAsync(() -> "resultA").thenAccept(resultA -> {});
//任務 A 執行完執行 B,B 需要 A 的結果,同時任務 B 有返回值
CompletableFuture.supplyAsync(() -> "resultA").thenApply(resultA -> resultA + " resultB");
前面 3 句代碼:演示的是,任務 A 無返回值。第 2 行和第 3 行代碼中,resultA 其實是 null。
第 4 句:用的是 thenRun(Runnable runnable),任務 A 執行完執行 B,並且 B 不需要 A 的結果。
第 5 句:用的是 thenAccept(Consumer action),任務 A 執行完執行 B,B 需要 A 的結果,但是任務 B 不返回值。
第 6 句:用的是 thenApply(Function fn),任務 A 執行完執行 B,B 需要 A 的結果,同時任務 B 有返回值。
這一小節說完了,如果任務 B 後面還有任務 C,往下繼續調用 .thenXxx() 即可。
2.2 取兩個任務的結果(並行)
我們來看怎麼讓任務 A 和任務 B 同時執行,然後取它們的結果進行後續操作,這裏強調的是任務之間的並行工作。
如果使用 Future 的話,我們通常是這麼寫的:
ExecutorService executorService = Executors.newCachedThreadPool();
Future<String> futureA = executorService.submit(() -> "resultA");
Future<String> futureB = executorService.submit(() -> "resultB");
String resultA = futureA.get();
String resultB = futureB.get();
接下來,我們看看 CompletableFuture 中是怎麼寫的:
CompletableFuture<String> futureA = CompletableFuture.supplyAsync(() -> "resultA");
CompletableFuture<String> futureB = CompletableFuture.supplyAsync(() -> "resultB");
//使用兩個任務的結果 resultA 和 resultB,thenAcceptBoth 表示後續的處理不需要返回值
futureA.thenAcceptBoth(futureB, (resultA, resultB) -> {});
//使用兩個任務的結果 resultA 和 resultB,而 thenCombine 表示需要返回值。
futureA.thenCombine(futureB, (resultA, resultB) -> "result A + B");
//如果你不需要 resultA 和 resultB,runAfterBoth 方法。
futureA.runAfterBoth(futureB, () -> {});
第 4句代碼:使用兩個任務的結果 resultA 和 resultB,thenAcceptBoth 表示後續的處理不需要返回值
第 5 句代碼:使用兩個任務的結果 resultA 和 resultB,而 thenCombine 表示需要返回值。
第 6句代碼:如果你不需要 resultA 和 resultB,runAfterBoth 方法。
2.3 取多個任務的結果(並行)
我們將介紹兩個非常簡單的靜態方法:allOf() 和 anyOf() 方法:
public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs){...}
public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs) {...}
- allOf :聚合了多個 CompletableFuture 實例,所以它是沒有返回值的
CompletableFuture futureA = CompletableFuture.supplyAsync(() -> "resultA");
CompletableFuture futureB = CompletableFuture.supplyAsync(() -> 123);
CompletableFuture futureC = CompletableFuture.supplyAsync(() -> "resultC");
//並行執行三個任務,但是由於是多個任務,沒有返回值
CompletableFuture<Void> future = CompletableFuture.allOf(futureA, futureB, futureC);
// 所以這裏的 join() 將阻塞,直到所有的任務執行結束
future.join();
- anyOf :就是隻要有任意一個 CompletableFuture 實例執行完成就可以了
CompletableFuture futureA = CompletableFuture.supplyAsync(() -> "resultA");
CompletableFuture futureB = CompletableFuture.supplyAsync(() -> 123);
CompletableFuture futureC = CompletableFuture.supplyAsync(() -> "resultC");
//三個任務只要有一個有返回值了就行
CompletableFuture<Object> future = CompletableFuture.anyOf(futureA, futureB, futureC);
//最後一行的 join() 方法會返回最先完成的任務的結果,
//所以它的泛型用的是 Object,因爲每個任務可能返回的類型不同
Object result = future.join();
2.4 異常處理
我們順便來說下 CompletableFuture 的異常處理。這裏我們要介紹兩個方法:
public CompletableFuture<T> exceptionally(Function<Throwable, ? extends T> fn);
public <U> CompletionStage<U> handle(BiFunction<? super T, Throwable, ? extends U> fn);
看下面的代碼:
CompletableFuture.supplyAsync(() -> "resultA")
.thenApply(resultA -> resultA + " resultB")
.thenApply(resultB -> resultB + " resultC")
.thenApply(resultC -> resultC + " resultD");
上面的代碼中,任務 A、B、C、D 依次執行,如果任務 A 拋出異常(當然上面的代碼不會拋出異常),那麼後面的任務都得不到執行
方式一:
我們在任務 A 中拋出異常,並對其進行處理
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
throw new RuntimeException();
})
.exceptionally(ex -> "errorResultA")
.thenApply(resultA -> resultA + " resultB")
.thenApply(resultB -> resultB + " resultC")
.thenApply(resultC -> resultC + " resultD");
System.out.println(future.join());
方式二:
使用 handle(BiFunction fn) 來處理異常
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "resultA")
.thenApply(resultA -> resultA + " resultB")
// 任務 C 拋出異常
.thenApply(resultB -> {throw new RuntimeException();})
// 處理任務 C 的返回值或異常
.handle(new BiFunction<Object, Throwable, Object>() {
@Override
public Object apply(Object re, Throwable throwable) {
if (throwable != null) {
return "errorResultC";
}
return re;
}
})
.thenApply(resultC -> resultC + " resultD");
System.out.println(future.join());
上面的代碼使用了 handle 方法來處理任務 C 的執行結果,上面的代碼中,re 和 throwable 必然有一個是 null,它們分別代表正常的執行結果和異常的情況。
當然,它們也可以都爲 null,因爲如果它作用的那個 CompletableFuture 實例沒有返回值的時候,re 就是 null。
【三】Fock_Join
fork/join框架是ExecutorService接口的一個實現,可以幫助開發人員充分利用多核處理器的優勢,編寫出並行執行的程序,提高應用程序的性能;設計的目的是爲了處理那些可以被遞歸拆分的任務。
fork/join框架與其它ExecutorService的實現類相似,會給線程池中的線程分發任務,不同之處在於它使用了工作竊取算法,所謂工作竊取,指的是對那些處理完自身任務的線程,會從其它線程竊取任務執行。
Fork/Join框架是Java 7提供的一個用於並行執行任務的框架,是一個把大任務分割成若干個小任務,最終彙總每個小任務結果後得到大任務結果的框架。簡單來說,Fork就是把一個大任務切分爲若干子任務並行的執行,Join就是合併這些子任務的執行結果,最後得到這個大任務的結果。比如計算1+2+…+10000,可以分割成10個子任務,每個子任務分別對1000個數進行求和,最終彙總這10個子任務的結果。
3.1 Fork/Join框架的工作原理
從上面Fork/Join框架的介紹中瞭解到,Fork/Join框架主要分爲幾個步驟實現。
1、分割任務。首先我們需要有一個fork類來把大任務分割成子任務,有可能子任務還是很大,所以還需要不停地分割,直到分割出的子任務足夠小。
2、執行任務併合並結果。分割的子任務分別放在雙端隊列裏,然後幾個啓動線程分別從雙端隊列裏獲取任務執行。子任務執行完的結果都統一放在一個隊列裏,啓動一個線程從隊列裏拿數據,然後合併這些數據。
- RecursiveAction:用於沒有返回結果的任務,即執行任務後不會返回結果;
- RecursiveTask:用於有返回結果的任務,即執行任務後會返回結果。
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.RecursiveTask;
public class CountTask extends RecursiveTask<Integer> {
// 閾值,表示拆分爲幾個子任務
private static final int THRESHOLD = 2;
private int start;
private int end;
public CountTask(int start, int end) {
this.start = start;
this.end = end;
}
@Override
protected Integer compute() {
int sum = 0;
// 如果任務小於等於閾值則計算任務
Boolean canCompute = (end - start) <= THRESHOLD;
if (canCompute) {
for (int i = start; i <= end; i++) {
sum += i;
}
} else {
// 如果任務大於閾值,就分裂成兩個子任務計算
int middle = (start + end) / 2;
CountTask leftTask = new CountTask(start, middle);
CountTask rightTask = new CountTask(middle + 1, end);
// 執行子任務
leftTask.fork();
rightTask.fork();
// 等待子任務執行完,並得到其結果
int leftResult=leftTask.join();
int rightResult=rightTask.join();
// 合併子任務
sum = leftResult + rightResult;
}
r
eturn sum;
}
public static void main(String[] args) {
ForkJoinPool forkJoinPool = new ForkJoinPool();
// 生成一個計算任務,負責計算1+2+3+4
CountTask task = new CountTask(1, 4);
// 執行一個任務
Future<Integer> result = forkJoinPool.submit(task);
try {
System.out.println(result.get());
}
catch (InterruptedException e) {
}
catch (ExecutionException e) {
}
}
}
【四】鏈接
https://blog.csdn.net/striveb/article/details/84140407
https://blog.csdn.net/w605283073/article/details/92418504