Java多線程 - Future模式

什么是Future模式

Future模式是多線程開發(fā)中非常常見的一種設(shè)計(jì)模式外邓。它的核心思想是異步調(diào)用。當(dāng)我們需要調(diào)用一個(gè)函數(shù)方法時(shí)。如果這個(gè)函數(shù)執(zhí)行很慢,那么我們就要進(jìn)行等待。但有時(shí)候,我們可能并不急著要結(jié)果。因此,我們可以讓被調(diào)用者立即返回,讓他在后臺(tái)慢慢處理這個(gè)請(qǐng)求培他。對(duì)于調(diào)用者來說,則可以先處理一些其他任務(wù),在真正需要數(shù)據(jù)的場合再去嘗試獲取需要的數(shù)據(jù)。

用生活中的例子來打個(gè)比喻,就像叫外賣遗座。比如在午休之前我們可以提前叫外賣,只需要點(diǎn)好食物,下個(gè)單舀凛。然后我們可以繼續(xù)工作。到了中午下班的時(shí)候外賣也就到了,然后就可以吃個(gè)午餐,再美滋滋的睡個(gè)午覺途蒋。而如果你在下班的時(shí)候才叫外賣,那就只能坐在那里干等著外賣小哥,最后拿到外賣吃完午飯,午休時(shí)間也差不多結(jié)束了猛遍。

使用Future模式,獲取數(shù)據(jù)的時(shí)候無法立即得到需要的數(shù)據(jù)。而是先拿到一個(gè)契約,你可以再將來需要的時(shí)候再用這個(gè)契約去獲取需要的數(shù)據(jù),這個(gè)契約就好比叫外賣的例子里的外賣訂單号坡。

用普通方式和Future模式的差別

我們可以看一下使用普通模式和用Future模式的時(shí)序圖懊烤。可以看出來普通模式是串行的,在遇到耗時(shí)操作的時(shí)候只能等待宽堆。而Future模式,只是發(fā)起了耗時(shí)操作,函數(shù)立馬就返回了,并不會(huì)阻塞客戶端線程腌紧。所以在工作線程執(zhí)行耗時(shí)操作的時(shí)候客戶端無需等待,可以繼續(xù)做其他事情,等到需要的時(shí)候再向工作線程獲取結(jié)果:

1.png

Future模式的簡單實(shí)現(xiàn)

首先是FutureData,它是只是一個(gè)包裝類,創(chuàng)建它不需要耗時(shí)。在工作線程準(zhǔn)備好數(shù)據(jù)之后可以使用setData方法將數(shù)據(jù)傳入畜隶。而客戶端線程只需要在需要的時(shí)候調(diào)用getData方法即可,如果這個(gè)時(shí)候數(shù)據(jù)還沒有準(zhǔn)備好,那么getData方法就會(huì)等待,如果已經(jīng)準(zhǔn)備好了就好直接返回壁肋。

public class FutureData<T> {
    private boolean mIsReady = false;
    private T mData;

    public synchronized void setData(T data) {
        mIsReady = true;
        mData = data;

        notifyAll();
    }

    public synchronized T getData() {
        while (!mIsReady) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return mData;
    }
}

接著是服務(wù)端,客戶端在向服務(wù)端請(qǐng)求數(shù)據(jù)的時(shí)候服務(wù)端不會(huì)實(shí)際去加載數(shù)據(jù),它只是創(chuàng)建一個(gè)FutureData,然后創(chuàng)建子線程去加載,而它只需要直接返回FutureData就可以了。

public class Server {
    public FutureData<String> getString() {
        final FutureData<String> data = new FutureData<>();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                data.setData("world");
            }
        }).start();

        return data;
    }
}

客戶端代碼如下,整個(gè)程序只需要運(yùn)行2秒多,但如果不使用Future模式的話就需要三秒了籽慢。

Server server = new Server();
FutureData<String> futureData = server.getString();

//先執(zhí)行其他操作
String hello = "hello";
try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    e.printStackTrace();
}

System.out.print(hello + " " + futureData.getData());

JDK中的Future模式

還記得我之前的一篇文章《Java多線程 - 線程池》中寫的ExecutorService.execute()和ExecutorService.submit()的區(qū)別嗎(如果沒有看過的讀者可以去看一下)浸遗?

execute方法其實(shí)是在Executor中定義的,而ExecutorService繼承了Executor。它只是簡單的提交了一個(gè)Runnable給線程池中的線程去調(diào)用:

public interface Executor {
    void execute(Runnable command);
}

public interface ExecutorService extends Executor {
    ...
}

而submit方法是ExecutorService中定義的,它們都會(huì)返回一個(gè)Future對(duì)象箱亿。實(shí)際上submit方法就是使用的Future模式:

public interface ExecutorService extends Executor {
    ...
    <T> Future<T> submit(Callable<T> task);
        
    <T> Future<T> submit(Runnable task, T result);
        
    Future<?> submit(Runnable task);
    ...
}

Future<?> submit(Runnable task) :

它的返回值實(shí)際上是Future<Void>,子線程是不會(huì)返回?cái)?shù)據(jù)的跛锌。

<T> Future<T> submit(Runnable task, T result) :

這個(gè)方法是不是很蛋疼,返回的結(jié)果在調(diào)用的時(shí)候已經(jīng)給出了。如果我一開始就知道結(jié)果那我為什么又要發(fā)起子線程呢届惋?

其實(shí)不然,這個(gè)result可以是一個(gè)代理,它不是實(shí)際的結(jié)果,它只是存儲(chǔ)了結(jié)果髓帽。我這里給出一個(gè)例子大家體會(huì)一下吧:

final String[] result = new String[1];

Runnable r = new Runnable() {
    public void run() {
        result[0] = "hello world";
    }
};

Future<String[]> future = Executors.newSingleThreadExecutor().submit(r, result);
    
try {
    System.out.println("result[0]: " + future.get()[0]);
} catch (InterruptedException e) {
    e.printStackTrace();
} catch (ExecutionException e) {
    e.printStackTrace();
}

<T> Future<T> submit(Callable<T> task) :

這個(gè)方法就比較好理解了, Callable.call()方法在子線程中被調(diào)用,同時(shí)它有返回值,只有將加載的數(shù)據(jù)直接return出來就好:

Future<String> future = Executors.newSingleThreadExecutor()
        .submit(new Callable<String>() {
            @Override
            public String call() throws Exception {
                return "Hello World";
            }
        });

try {
    System.out.print(future.get());
} catch (InterruptedException e) {
    e.printStackTrace();
} catch (ExecutionException e) {
    e.printStackTrace();
}

一個(gè)實(shí)際的例子

比如我們?cè)谟?jì)算兩個(gè)List<Integer>中的數(shù)的總和的時(shí)候就可以用Future模式提高效率:

public int getTotal(final List<Integer> a, final List<Integer> b) throws ExecutionException, InterruptedException {
    Future<Integer> future = Executors.newCachedThreadPool().submit(new Callable<Integer>() {
        @Override
        public Integer call() throws Exception {
            int r = 0;
            for (int num : a) {
                r += num;
            }
            return r;
        }
    });

    int r = 0;
    for (int num : b) {
        r += num;
    }
    return r + future.get();
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市脑豹,隨后出現(xiàn)的幾起案子郑藏,更是在濱河造成了極大的恐慌,老刑警劉巖晨缴,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異峡捡,居然都是意外死亡击碗,警方通過查閱死者的電腦和手機(jī)筑悴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來稍途,“玉大人阁吝,你說我怎么就攤上這事⌒蹬模” “怎么了突勇?”我有些...
    開封第一講書人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長坷虑。 經(jīng)常有香客問我甲馋,道長,這世上最難降的妖魔是什么迄损? 我笑而不...
    開封第一講書人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任定躏,我火速辦了婚禮,結(jié)果婚禮上芹敌,老公的妹妹穿的比我還像新娘痊远。我一直安慰自己,他們只是感情好氏捞,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開白布碧聪。 她就那樣靜靜地躺著,像睡著了一般液茎。 火紅的嫁衣襯著肌膚如雪逞姿。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評(píng)論 1 285
  • 那天豁护,我揣著相機(jī)與錄音哼凯,去河邊找鬼。 笑死楚里,一個(gè)胖子當(dāng)著我的面吹牛断部,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播班缎,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼蝴光,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了达址?” 一聲冷哼從身側(cè)響起蔑祟,我...
    開封第一講書人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎沉唠,沒想到半個(gè)月后疆虚,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年径簿,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了罢屈。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡篇亭,死狀恐怖缠捌,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情译蒂,我是刑警寧澤曼月,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站柔昼,受9級(jí)特大地震影響哑芹,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜岳锁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一绩衷、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧激率,春花似錦咳燕、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至嘉冒,卻和暖如春曹货,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背讳推。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國打工顶籽, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人银觅。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓礼饱,卻偏偏與公主長得像,于是被迫代替她去往敵國和親究驴。 傳聞我的和親對(duì)象是個(gè)殘疾皇子镊绪,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

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