Java併發程式設計非同步操作Future和FutureTask

渣男小四發表於2022-03-26

碼農在囧途

生活是一個洗禮自己的過程,這個洗禮並不是傳統意義上的洗禮,傳統意義上的洗禮通常認為這個人的思想得到洗禮,靈魂得到洗禮,十分的清新脫俗,不世故,不圓滑,而現實的洗禮實則是讓一個人褪去幼稚,褪去無知,讓你變得點頭哈腰,圓滑世故,我們都是動物,需要物質滿足,更需要慾望填補,所以,變成自己小時候唾罵的物件也是可以理解,不過這是一個選擇,你可以進行選擇,只是在物慾橫流的時代,多數人沒有這種選擇的權力!

Future和FutureTask

Future是一個介面,FutureTask是一個類,實現RunnableFuture介面,RunnableFuture介面繼承Future介面。

Future介面的方法

V get() :獲取非同步執行的結果,如果沒有返回結果,此方法會阻塞直到非同步計算完成。

V get(Long timeout , TimeUnit unit) :獲取非同步執行結果,如果沒有結果可用,此方法會阻塞,但是會有時間限制,如果阻塞時間超過設定的timeout時間,該方法將丟擲異常。

boolean isDone() :如果任務執行結束,無論是正常結束或是中途取消還是發生異常,都返回true。

boolean isCancelled() :如果任務完成前被取消,則返回true。

boolean cancel(boolean mayInterruptRunning) :如果任務還沒開始,執行cancel(...)方法將返回false;如果任務已經啟動, 執行cancel(true)方法將以中斷執行此任務執行緒的方式來試圖停止任務,如果停止成功,返回true;當任務已經啟動, 執行cancel(false)方法將不會對正在執行的任務執行緒產生影響(讓執行緒正常執行到完成),此時返回false;當任務已經完成, 執行cancel(...)方法將返回false。mayInterruptRunning參數列示是否中斷執行中的執行緒。

Future是一個介面,因此我們不能直接建立物件,需要配合執行緒池一起使用,FutureTask我們可以直接建立物件。

Future的使用

Future代表非同步執行的結果,也就是說非同步執行完畢後,結果儲存在Future裡, 我們在使用執行緒池submit()時需要傳入Callable介面,執行緒池的返回值為一個Future,而Future則儲存了執行的結果 ,可通過Futureget()方法取出結果,如果執行緒池使用的是execute()方法,則傳入的是Runnable介面無返回值。

如下我們使用Future模擬下單操作,使用者下單後儲存訂單資訊扣減庫存增加積分傳送簡訊通知,這麼多個任務如果使用同步執行,那麼效率就會 比較低,使用者體驗不好,一般我們會採用訊息佇列來達到非同步的效果,今天我們就不用訊息佇列,而是使用Future介面來實現非同步。

public class FutureTest {
    final static ExecutorService threadPool = Executors.newCachedThreadPool();
​
    //儲存訂單任務
    public static Future<R> saveOrderTask(OrderInfo orderInfo) {
        return threadPool.submit(new Callable<R>() {
            @Override
            public R call() throws Exception {
                return saveOrder(orderInfo);
            }
        });
    }
​
    //扣減庫存任務
    public static Future<R> decreaseStockTask(OrderInfo orderInfo) {
        return threadPool.submit(new Callable<R>() {
            @Override
            public R call() throws Exception {
                return decreaseStockByCommodityId(orderInfo);
            }
        });
    }
​
    //增加積分任務
    public static Future<R> increaseIntegralTask(OrderInfo orderInfo) {
        return threadPool.submit(new Callable<R>() {
            @Override
            public R call() throws Exception {
                return increaseIntegralByUserId(orderInfo);
            }
        });
    }
​
    public static void sendMsgToPhone(OrderInfo orderInfo) {
        threadPool.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("使用者【" + orderInfo.getUserId() + "】,你已下單成功~~~~~~~~");
            }
        });
    }
​
    //增加積分rpc介面
    public static R increaseIntegralByUserId(OrderInfo orderInfo) {
        System.out.println("增加積分~~~~~~~~");
        integralService.increaseIntegralByUserId(orderInfo.getUserId(),20);
        return new R(200, "增加積分成功", null);
    }
​
    //扣減庫存rpc介面
    public static R decreaseStockByCommodityId(OrderInfo orderInfo) {
        System.out.println("扣減庫存~~~~~~~~");
        stockService.decreaseStockByCommodityId(orderInfo.getCommodityId());
        return new R(200, "扣減庫存成功", null);
    }
​
    //儲存訂單rpc介面
    public static R saveOrder(OrderInfo orderInfo) throws InterruptedException {
        System.out.println("儲存訂單~~~~~~~~");
        Thread.sleep(2000);
        orderService.insert(orderInfo);
        return new R(200, "儲存訂單成功", null);
    }
​
​
    public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
        OrderInfo orderInfo = new OrderInfo().setId("123455").setUserId("111111").setCommodityId("123321");
        Future<R> orderTask = saveOrderTask(orderInfo);
        Future<R> stockTask = decreaseStockTask(orderInfo);
        Future<R> integralTask = increaseIntegralTask(orderInfo);
        sendMsgToPhone(orderInfo);
        if (orderTask.get().getCode() == 200 && orderTask.isDone()) 
            System.out.println(orderTask.get().getMsg());
        if (stockTask.get().getCode() == 200 && stockTask.isDone()) 
            System.out.println(stockTask.get().getMsg());
        if (integralTask.get().getCode() == 200 && integralTask.isDone()) 
            System.out.println(integralTask.get().getMsg());
        threadPool.shutdownNow();
    }
}

輸出

儲存訂單~~~~~~~~
扣減庫存~~~~~~~~
增加積分~~~~~~~~
使用者【111111】,你已下單成功~~~~~~~~
儲存訂單成功
扣減庫存成功
增加積分成功

我們在儲存訂單介面模擬處理業務操作,花費了2s,從輸出結果可以看出,其他rpc介面並沒有在儲存訂單時而阻塞,而是同時執行,就達到了非同步的效果。

不過我們發現了一個問題,那就是非同步返回結果被阻塞了,我明明我扣減庫存和增加積分介面很快就返回,但是從輸出中卻發現扣減庫存和增加積分在儲存 訂單後輸出,由此我們可看出Future會阻塞返回結果。

上面我們傳送簡訊到使用者手機並沒有獲取返回結果,所以沒有使用Future,使用執行緒池我們就沒有使用Callable介面,而是使用Runnable介面, 方法就是execute(),而不是submit()

execute()和submit()區別

1.execute無返回值,這樣就無法知道任務是否執行成功,而submit有返回值。 2.execute丟擲異常後無法處理,不能捕捉異常,而submit可以捕獲異常;

FutureTask的使用

FutureTaskFuture介面的實現類,我們可以直接建立一個FutureTask物件,下面我們對上面的下單流程就行改造,使用FutureTask 來實現。

/**
 * @author 劉牌
 * @date 2022/3/2617:34
 */
public class PlaceOrderFutureTaskTest {
    final static ExecutorService threadPool = Executors.newCachedThreadPool();
​
    //儲存訂單任務
    public static FutureTask<R> saveOrderTask(OrderInfo orderInfo){
        return new FutureTask<>(new Callable<R>() {
            @Override
            public R call() throws Exception {
                return saveOrder(orderInfo);
            }
        });
    }
​
    //扣減庫存任務
    public static FutureTask<R> decreaseStockTask(OrderInfo orderInfo){
        return new FutureTask<>(new Callable<R>() {
            @Override
            public R call() throws Exception {
                return decreaseStockByCommodityId(orderInfo);
            }
        });
    }
​
    //增加積分任務
    public static FutureTask<R> increaseIntegralTask(OrderInfo orderInfo){
        return new FutureTask<>(new Callable<R>() {
            @Override
            public R call() throws Exception {
                return increaseIntegralByUserId(orderInfo);
            }
        });
    }
​
    public static void sendMsgToPhone(OrderInfo orderInfo){
        threadPool.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("使用者【"+orderInfo.getUserId()+"】,你已下單成功~~~~~~~~");
            }
        });
    }
​
    //增加積分rpc介面
    public static R increaseIntegralByUserId(OrderInfo orderInfo){
        System.out.println("增加積分~~~~~~~~");
        integralService.increaseIntegralByUserId(orderInfo.getUserId(),20);
        return new R(200,"增加積分成功",null);
    }
​
    //扣減庫存rpc介面
    public static R decreaseStockByCommodityId(OrderInfo orderInfo){
        System.out.println("扣減庫存~~~~~~~~");
        stockService.decreaseStockByCommodityId(orderInfo.getCommodityId());
        return new R(200,"扣減庫存成功",null);
    }
​
    //儲存訂單rpc介面
    public static R saveOrder(OrderInfo orderInfo) throws InterruptedException {
        System.out.println("儲存訂單~~~~~~~~");
        Thread.sleep(2000);
        orderService.insert(orderInfo);
        return new R(200,"儲存訂單成功",null);
    }
    
    public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
        OrderInfo orderInfo = new OrderInfo().setId("123455").setUserId("111111").setCommodityId("123321");
        FutureTask<R> orderTask = saveOrderTask(orderInfo);
        FutureTask<R> stockTask = decreaseStockTask(orderInfo);
        FutureTask<R> integralTask = increaseIntegralTask(orderInfo);
        threadPool.submit(orderTask);
        threadPool.submit(stockTask);
        threadPool.submit(integralTask);
        sendMsgToPhone(orderInfo);
        if (orderTask.get().getCode() == 200 && orderTask.isDone()) 
            System.out.println(orderTask.get().getMsg());
        if (stockTask.get().getCode() == 200 && stockTask.isDone()) 
            System.out.println(stockTask.get().getMsg());
        if (integralTask.get().getCode() == 200 && integralTask.isDone()) 
            System.out.println(integralTask.get().getMsg());
        threadPool.shutdownNow();
    }
}

輸出

儲存訂單~~~~~~~~
扣減庫存~~~~~~~~
增加積分~~~~~~~~
使用者【111111】,你已下單成功~~~~~~~~
儲存訂單成功
扣減庫存成功
增加積分成功

額~~~,從程式碼中我們看出其實沒啥區別,就是一個介面和實現類的不同寫法而已,從輸入也可以看出和上面的Future一樣,由此可知FutureTask獲取結果也是 阻塞的。

總結

從上面的流程中可以看出,FutureFutureTask能夠實現非同步,但是獲取結果卻是同步的,這缺陷也是顯而易見,如果遇到耗時的任務,那麼獲取返回值的時候 其他任務就會被阻塞,只能排隊慢慢來,在高併發的場景下不適合,那有沒有解決方案呢,肯定有的,那就是CompletableFuture,我們後面繼續介紹,本章我們 就不對其進行介紹。

今天的分享就到這裡,感謝你的觀看,我們下期見。

相關文章