前言: 最近在看RPC框架的書,書裏提到了Future的使用,發現我對這個並不是很瞭解,所以自學一下Future。
1 Future是什麼:
在併發編程中,我們經常用到非阻塞的模型,在之前的多線程的三種實現中,不管是繼承thread類還是實現runnable接口,都無法保證獲取到之前的執行結果。通過實現Callback接口,並用Future可以來接收多線程的執行結果。Future表示一個可能還沒有完成的異步任務的結果,針對這個結果可以添加Callback以便在任務執行成功或失敗後作出相應的操作。
尋找資料的過程中,發現了一個博主寫的有趣的案例,看完後我覺得我有一個可以不用Future就能解決的方案,測試了,和他的時間一樣,寫出來大家看看,我想看完這個之後大家會更容易理解Future的
該博主的問題場景是:要做菜,得先去買廚具,買菜,這兩樣都齊全了之後纔可以開始做菜。
一般的操作邏輯可能是:先去買菜或者先去買廚具,是串行邏輯,導致效率低下,該博主就想用Future去優化這個操作邏輯,是他們變成並行的操作。
package com.kinglong.springboot.service.impl;
/**
* 原問題:串行邏輯導致效率低下
*
* @author haojinlong
* @date 2019/7/2815:10
*/
public class CommonCook01 {
public static void main(String[] args) throws InterruptedException {
long startTime = System.currentTimeMillis();
// 第一步 網購廚具
OnlineShopping thread = new OnlineShopping();
thread.start();
thread.join(); // 保證廚具送到
// 第二步 去超市購買食材
Thread.sleep(2000); // 模擬購買食材時間
Shicai shicai = new Shicai();
System.out.println("第二步:食材到位");
// 第三步 用廚具烹飪食材
System.out.println("第三步:開始展現廚藝");
cook(thread.chuju, shicai);
System.out.println("總共用時" + (System.currentTimeMillis() - startTime) + "ms");
}
// 網購廚具線程
static class OnlineShopping extends Thread {
private Chuju chuju;
@Override
public void run() {
System.out.println("第一步:下單");
System.out.println("第一步:等待送貨");
try {
Thread.sleep(5000); // 模擬送貨時間
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第一步:快遞送到");
chuju = new Chuju();
}
}
// 用廚具烹飪食材
static void cook(Chuju chuju, Shicai shicai) {
}
// 廚具類
static class Chuju {
}
// 食材類
static class Shicai {
}
}
原邏輯耗時:
第一步:下單
第一步:等待送貨
第一步:快遞送到
第二步:食材到位
第三步:開始展現廚藝
總共用時7005ms
該博主優化後的
package com.kinglong.springboot.service.impl;
import java.util.concurrent.*;
/**
* 該博主的方案:使用FutureTask實現
*
* @author haojinlong
* @date 2019/7/2815:10
*/
public class CommonCook02 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
long startTime = System.currentTimeMillis();
// 第一步 網購廚具
Callable<Chuju> onlineShopping = new Callable<Chuju>() {
@Override
public Chuju call() throws Exception {
System.out.println("第一步:下單");
System.out.println("第一步:等待送貨");
Thread.sleep(5000); // 模擬送貨時間
System.out.println("第一步:快遞送到");
return new Chuju();
}
};
FutureTask<Chuju> task = new FutureTask<Chuju>(onlineShopping);
new Thread(task).start();
// 第二步 去超市購買食材
Thread.sleep(2000); // 模擬購買食材時間
Shicai shicai = new Shicai();
System.out.println("第二步:食材到位");
// 第三步 用廚具烹飪食材
if (!task.isDone()) { // 聯繫快遞員,詢問是否到貨
System.out.println("第三步:廚具還沒到,心情好就等着(心情不好就調用cancel方法取消訂單)");
}
Chuju chuju = task.get();
System.out.println("第三步:廚具到位,開始展現廚藝");
cook(chuju, shicai);
System.out.println("總共用時" + (System.currentTimeMillis() - startTime) + "ms");
}
// 用廚具烹飪食材
static void cook(Chuju chuju, Shicai shicai) {}
// 廚具類
static class Chuju {}
// 食材類
static class Shicai {}
}
該博主優化後的耗時
第一步:下單
第一步:等待送貨
第二步:食材到位
第三步:廚具還沒到,心情好就等着(心情不好就調用cancel方法取消訂單)
第一步:快遞送到
第三步:廚具到位,開始展現廚藝
總共用時5105ms
以下是我的實現
package com.kinglong.springboot.service.impl;
import java.util.concurrent.*;
/**
* 我的方案:使用CountDownLatch實現
*
* @author haojinlong
* @date 2019/7/2815:10
*/
public class CommonCook03 {
private static Chuju chuju = null;
private static Shicai shicai = null;
public static void main(String[] args) throws InterruptedException, ExecutionException {
long startTime = System.currentTimeMillis();
CountDownLatch countDownLatch = new CountDownLatch(2);
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(2);
// 第一步 網購廚具
OnlineShopping(fixedThreadPool,countDownLatch);
// 第二步 去超市購買食材
BuyShiCai(fixedThreadPool, countDownLatch);
countDownLatch.await();
cook(chuju, shicai);
System.out.println("總共用時" + (System.currentTimeMillis() - startTime) + "ms");
}
static void OnlineShopping(ExecutorService fixedThreadPool,CountDownLatch countDownLatch){
fixedThreadPool.execute(new Runnable() {
public void run() {
System.out.println("第一步:下單");
System.out.println("第一步:等待送貨");
try {
Thread.sleep(5000); // 模擬送貨時間
} catch (InterruptedException e) {
countDownLatch.countDown();
e.printStackTrace();
}
System.out.println("第一步:快遞送到");
chuju = new Chuju();
System.out.println("第三步:廚具到位,開始展現廚藝");
countDownLatch.countDown();
}
});
}
static void BuyShiCai(ExecutorService fixedThreadPool,CountDownLatch countDownLatch){
fixedThreadPool.execute(new Runnable() {
public void run() {
try {
Thread.sleep(2000); // 模擬購買食材時間
} catch (InterruptedException e) {
countDownLatch.countDown();
e.printStackTrace();
}
shicai = new Shicai();
System.out.println("第二步:食材到位");
countDownLatch.countDown();
}
});
}
// 用廚具烹飪食材
static void cook(Chuju chuju, Shicai shicai) {}
// 廚具類
static class Chuju {}
// 食材類
static class Shicai {}
}
我的實現耗時
第一步:下單
第一步:等待送貨
第二步:食材到位
第一步:快遞送到
第三步:廚具到位,開始展現廚藝
總共用時5033ms
總結:
從程序的執行時間可以看出來,我的方案和答主的方案耗時其實相差不大,雙方的思路一樣,都是將串行變並行,不同的是它採用的是Future而我採用的是countDownLatch,因爲我平時使用countDownLatch場景較多,比較熟悉。
說到這,想必大家也差不多明白了Future的使用場景,也就是用在異步執行某個請求,並且能夠返回執行結果。