【并發(fā)編程】Future模式及JDK中的實(shí)現(xiàn)

1.1、Future模式是什么

先簡單舉個(gè)例子介紹堪伍,當(dāng)我們平時(shí)寫一個(gè)函數(shù),函數(shù)里的語句一行行同步執(zhí)行觅闽,如果某一行執(zhí)行很慢帝雇,程序就必須等待,直到執(zhí)行結(jié)束才返回結(jié)果蛉拙;但有時(shí)我們可能并不急著需要其中某行的執(zhí)行結(jié)果尸闸,想讓被調(diào)用者立即返回。比如小明在某網(wǎng)站上成功創(chuàng)建了一個(gè)賬號孕锄,創(chuàng)建完賬號后會(huì)有郵件通知吮廉,如果在郵件通知時(shí)因某種原因耗時(shí)很久(此時(shí)賬號已成功創(chuàng)建),使用傳統(tǒng)同步執(zhí)行的方式那就要等完這個(gè)時(shí)間才會(huì)有創(chuàng)建成功的結(jié)果返回到前端畸肆,但此時(shí)賬號創(chuàng)建成功后我們并不需要立即關(guān)心郵件發(fā)送成功了沒宦芦,此時(shí)就可以使用Future模式,讓安在后臺慢慢處理這個(gè)請求轴脐,對于調(diào)用者來說调卑,則可以先處理一些其他任務(wù),在真正需要數(shù)據(jù)的場合(比如某時(shí)想要知道郵件發(fā)送是否成功)再去嘗試獲取需要的數(shù)據(jù)大咱。

使用Future模式,獲取數(shù)據(jù)的時(shí)候可能無法立即得到需要的數(shù)據(jù)恬涧。而是先拿到一個(gè)包裝,可以在需要的時(shí)候再去get獲取需要的數(shù)據(jù)。

1.2碴巾、Future模式與傳統(tǒng)模式的區(qū)別

先看看請求返回的時(shí)序圖溯捆,明顯傳統(tǒng)的模式是串行同步執(zhí)行的,在遇到耗時(shí)操作的時(shí)候只能等待。反觀Future模式餐抢,發(fā)起一個(gè)耗時(shí)操作后现使,函數(shù)會(huì)立刻返回,并不會(huì)阻塞客戶端線程旷痕。所以在執(zhí)行實(shí)際耗時(shí)操作時(shí)候客戶端無需等待碳锈,可以做其他事情,直到需要的時(shí)候再向工作線程獲取結(jié)果。


future.png
2.1欺抗、動(dòng)手實(shí)現(xiàn)簡易Future模式

下面的DataFuture類只是一個(gè)包裝類售碳,創(chuàng)建它時(shí)無需阻塞等待。在工作線程準(zhǔn)備好數(shù)據(jù)后使用setRealData方法將數(shù)據(jù)傳入绞呈∶橙耍客戶端只要在真正需要數(shù)據(jù)時(shí)調(diào)用getRealData方法即可,如果此時(shí)數(shù)據(jù)已準(zhǔn)備好則立即返回佃声,否則getRealData方法就會(huì)等待艺智,直到獲取數(shù)據(jù)完成。

public class DataFuture<T> {
    private T realData;
    private boolean isOK = false;

    public synchronized T getRealData() {
        while (!isOK) {
            try {
                // 數(shù)據(jù)未準(zhǔn)備好則等待
                wait();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return realData;
    }
    
    public synchronized void setRealData(T data) {
        isOK = true;
        realData = data;
        notifyAll();
    }
}

下面實(shí)現(xiàn)一服務(wù)端圾亏,客戶端向服務(wù)端請求數(shù)據(jù)時(shí)十拣,服務(wù)端并不會(huì)立刻去加載真正數(shù)據(jù),只是創(chuàng)建一個(gè)DataFuture志鹃,創(chuàng)建子線程去加載真正數(shù)據(jù)夭问,服務(wù)端直接返回DataFuture即可。

public class Server {
    
    public DataFuture<String> getData() {
        final DataFuture<String> data = new DataFuture<>();
        Executors.newSingleThreadExecutor().execute(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                data.setRealData("最終數(shù)據(jù)");
            }
        });
        return data;
    }
}

最終客戶端調(diào)用 代碼如下:

long start = System.currentTimeMillis();
Server server = new Server();
DataFuture<String> dataFuture = server.getData();

try {
    // 先執(zhí)行其他操作
    Thread.sleep(5000);
    // 模擬耗時(shí)...
} catch (InterruptedException e) {
    e.printStackTrace();
}

System.out.print("結(jié)果數(shù)據(jù):" + dataFuture.getRealData());
System.out.println("耗時(shí): " + (System.currentTimeMillis() - start));

結(jié)果:

結(jié)果數(shù)據(jù):最終數(shù)據(jù)
耗時(shí): 5021

執(zhí)行最終數(shù)據(jù)耗時(shí)都在5秒左右曹铃,如果串行執(zhí)行的話就是10秒左右。

2.2陕见、JDK中的Future與FutureTask

先來看看Future接口源碼:

public interface Future<V> {

    /**
     * 用來取消任務(wù),取消成功則返回true淳玩,取消失敗則返回false。
     * mayInterruptIfRunning參數(shù)表示是否允許取消正在執(zhí)行卻沒有執(zhí)行完畢的任務(wù)蜕着,設(shè)為true谋竖,則表示可以取消正在執(zhí)行過程中的任務(wù)承匣。
     * 如果任務(wù)已完成蓖乘,則無論mayInterruptIfRunning為true還是false韧骗,此方法都返回false嘉抒,即如果取消已經(jīng)完成的任務(wù)會(huì)返回false;
     * 如果任務(wù)正在執(zhí)行袍暴,若mayInterruptIfRunning設(shè)置為true隶症,則返回true岗宣,若mayInterruptIfRunning設(shè)置為false蚂会,則返回false耗式;
     * 如果任務(wù)還沒有執(zhí)行,則無論mayInterruptIfRunning為true還是false彪见,肯定返回true娱挨。
     */
    boolean cancel(boolean mayInterruptIfRunning);

    /**
     * 表示任務(wù)是否被取消成功,如果在任務(wù)正常完成前被取消成功让蕾,則返回true
     */
    boolean isCancelled();

    /**
     * 表示任務(wù)是否已經(jīng)完成,若任務(wù)完成笋婿,則返回true
     */
    boolean isDone();

    /**
     * 獲取執(zhí)行結(jié)果顿颅,如果最終結(jié)果還沒得出該方法會(huì)產(chǎn)生阻塞,直到任務(wù)執(zhí)行完畢返回結(jié)果
     */
    V get() throws InterruptedException, ExecutionException;

    /**
     * 獲取執(zhí)行結(jié)果粱腻,如果在指定時(shí)間內(nèi),還沒獲取到結(jié)果捞慌,則拋出TimeoutException
     */
    V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}

從上面源碼可看出Future就是對于Runnable或Callable任務(wù)的執(zhí)行進(jìn)行查詢柬批、中斷任務(wù)、獲取結(jié)果氮帐。下面就以一個(gè)計(jì)算1到1億的和為例子,看使用傳統(tǒng)方式和使用Future耗時(shí)差多少皮服。先看傳統(tǒng)方式代碼:

public class FutureTest {

    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        List<Integer> retList = new ArrayList<>();

        // 計(jì)算1000次1至1億的和
        for (int i = 0; i < 1000; i++) {
            retList.add(Calc.cal(100000000));
        }
        System.out.println("耗時(shí): " + (System.currentTimeMillis() - start));

        for (int i = 0; i < 1000; i++) {
            try {
                Integer result = retList.get(i);
                System.out.println("第" + i + "個(gè)結(jié)果: " + result);
            } catch (Exception e) {
            }
        }
        System.out.println("耗時(shí): " + (System.currentTimeMillis() - start));
    }

    public static class Calc implements Callable<Integer> {

        @Override
        public Integer call() throws Exception {
            return cal(10000);
        }

        public static int cal (int num) {
            int sum = 0;
            for (int i = 0; i < num; i++) {
                sum += i;
            }
            return sum;
        }
    }
}

執(zhí)行結(jié)果(耗時(shí)40+秒):

耗時(shí): 43659
第0個(gè)結(jié)果: 887459712
第1個(gè)結(jié)果: 887459712
第2個(gè)結(jié)果: 887459712
...
第999個(gè)結(jié)果: 887459712
耗時(shí): 43688

再來看看使用Future模式下程序:

public class FutureTest {

    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        ExecutorService executorService = Executors.newCachedThreadPool();
        List<Future<Integer>> futureList = new ArrayList<>();

        // 計(jì)算1000次1至1億的和
        for (int i = 0; i < 1000; i++) {
            // 調(diào)度執(zhí)行
            futureList.add(executorService.submit(new Calc()));
        }
        System.out.println("耗時(shí): " + (System.currentTimeMillis() - start));

        for (int i = 0; i < 1000; i++) {
            try {
                Integer result = futureList.get(i).get();
                System.out.println("第" + i + "個(gè)結(jié)果: " + result);
            } catch (InterruptedException | ExecutionException e) {
            }
        }
        System.out.println("耗時(shí): " + (System.currentTimeMillis() - start));
    }

    public static class Calc implements Callable<Integer> {

        @Override
        public Integer call() throws Exception {
            return cal(100000000);
        }

        public static int cal (int num) {
            int sum = 0;
            for (int i = 0; i < num; i++) {
                sum += i;
            }
            return sum;
        }
    }
}

執(zhí)行結(jié)果(耗時(shí)12+秒):

耗時(shí): 12058
第0個(gè)結(jié)果: 887459712
第1個(gè)結(jié)果: 887459712
...
第999個(gè)結(jié)果: 887459712
耗時(shí): 12405

可以看到龄广,計(jì)算1000次1至1億的和,使用Future模式并發(fā)執(zhí)行最終的耗時(shí)比使用傳統(tǒng)的方式快了30秒左右舟铜,使用Future模式的效率大大提高奠衔。

2.3塘娶、FutureTask

說完Future,F(xiàn)uture因?yàn)槭墙涌诓荒苤苯佑脕韯?chuàng)建對象刁岸,就有了下面的FutureTask。
先看看FutureTask的實(shí)現(xiàn):

public class FutureTask<V> implements RunnableFuture<V>

可以看到FutureTask類實(shí)現(xiàn)了RunnableFuture接口虹曙,接著看RunnableFuture接口源碼:

public interface RunnableFuture<V> extends Runnable, Future<V> {
    /**
     * Sets this Future to the result of its computation
     * unless it has been cancelled.
     */
    void run();
}

可以看到RunnableFuture接口繼承了Runnable接口和Future接口,也就是說其實(shí)FutureTask既可以作為Runnable被線程執(zhí)行矾踱,也可以作為Future得到Callable的返回值疏哗。

看下面FutureTask的兩個(gè)構(gòu)造方法,可以看出就是為這兩個(gè)操作準(zhǔn)備的返奉。

public FutureTask(Callable<V> var1) {
    if (var1 == null) {
        throw new NullPointerException();
    } else {
        this.callable = var1;
        this.state = 0;
    }
}

public FutureTask(Runnable var1, V var2) {
    this.callable = Executors.callable(var1, var2);
    this.state = 0;
}

FutureTask使用實(shí)例:

public class FutureTest {

    public static void main(String[] args) {
        ExecutorService executor = Executors.newCachedThreadPool();
        Calc task = new Calc();
        FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
        executor.submit(futureTask);
        executor.shutdown();
    }

    public static class Calc implements Callable<Integer> {

        @Override
        public Integer call() throws Exception {
            return cal(100000000);
        }

        public static int cal (int num) {
            int sum = 0;
            for (int i = 0; i < num; i++) {
                sum += i;
            }
            return sum;
        }
    }
}
2.4芽偏、Future不足之處

上面例子可以看到使用Future模式比傳統(tǒng)模式效率明顯提高了,使用Future一定程度上可以讓一個(gè)線程池內(nèi)的任務(wù)異步執(zhí)行污尉;但同時(shí)也有個(gè)明顯的缺點(diǎn):就是回調(diào)無法放到與任務(wù)不同的線程中執(zhí)行,傳統(tǒng)回調(diào)最大的問題就是不能將控制流分離到不同的事件處理器中等太。比如主線程要等各個(gè)異步執(zhí)行線程返回的結(jié)果來做下一步操作蛮放,就必須阻塞在future.get()方法等待結(jié)果返回,這時(shí)其實(shí)又是同步了,如果遇到某個(gè)線程執(zhí)行時(shí)間太長時(shí)压真,那情況就更糟了蘑险。

到Java8時(shí)引入了一個(gè)新的實(shí)現(xiàn)類CompletableFuture,彌補(bǔ)了上面的缺點(diǎn)佃迄,在下篇會(huì)講解CompletableFuture的使用。

作者注:原文發(fā)表在公號(點(diǎn)擊查看)堆缘,定期分享IT互聯(lián)網(wǎng)普碎、金融等工作經(jīng)驗(yàn)心得、人生感悟麻车,歡迎訂閱交流,目前就職阿里-移動(dòng)事業(yè)部啤斗,需要大廠內(nèi)推的也可到公眾號砸簡歷赁咙,或查看我個(gè)人資料獲取。(公號ID:weknow619)序目。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末猿涨,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子叛赚,更是在濱河造成了極大的恐慌,老刑警劉巖肥卡,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件事镣,死亡現(xiàn)場離奇詭異,居然都是意外死亡氛琢,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進(jìn)店門骚勘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來撮奏,“玉大人,你說我怎么就攤上這事畜吊。” “怎么了定拟?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵青自,是天一觀的道長驱证。 經(jīng)常有香客問我,道長抹锄,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任获高,我火速辦了婚禮吻育,結(jié)果婚禮上布疼,老公的妹妹穿的比我還像新娘。我一直安慰自己砾层,他們只是感情好贱案,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著铸董,像睡著了一般。 火紅的嫁衣襯著肌膚如雪粟害。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天套鹅,我揣著相機(jī)與錄音汰具,去河邊找鬼。 笑死吟孙,一個(gè)胖子當(dāng)著我的面吹牛聚蝶,可吹牛的內(nèi)容都是我干的杰妓。 我是一名探鬼主播碘勉,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼验靡,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了胜嗓?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤初厚,失蹤者是張志新(化名)和其女友劉穎孙技,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體亚情,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡哈雏,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年衫生,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了土浸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,102評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡泪酱,死狀恐怖还最,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情拓轻,我是刑警寧澤,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布勿锅,位于F島的核電站枣氧,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜危纫,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一种蝶、第九天 我趴在偏房一處隱蔽的房頂上張望契耿。 院中可真熱鬧螃征,春花似錦、人聲如沸踢械。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽背率。三九已至嫩与,卻和暖如春交排,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背处坪。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工都许, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人分蓖。 一個(gè)月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓唱凯,卻偏偏與公主長得像睛低,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子钱雷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評論 2 355

推薦閱讀更多精彩內(nèi)容