分享

多线程设计模式——Future模式

 CevenCheng 2011-10-18

在做应用开发时,有时会遇到一种场景:在完成某个主任务的同时,需要处理一些其它的子任务,为了加快响应速度,会将子任务放在另外的线程中执行。例如,搜索引擎在处理用户的查询请求时,不仅要从本地数据库查找匹配的结果,同时可能会向远程的广告服务器请求广告数据,最后将搜索结果和广告一起返回给用户。在这个例子中,主线程从本地查找搜索结果,同时启动子线程从远程服务器获取广告数据,当主线程查找结束时,会将子线程得到的广告数据与搜索结果合并,最后发送给用户。Java代码如下:

01public void searchService(query) {
02 
03    AdThread adThread = new AdThread();
04 
05    adThread.start();
06 
07    findResult(query);
08 
09    respond2User();
10}

主线程启动子线程后,子线程的执行已不受主线程的控制,其何时执行完毕,主线程无法预知,而其执行结果是由主线程来主动索取的。为了做到这一点,就需要用到Future模式。
Future模式是现实中提货单的抽象,好比去摄影店拍照,照片需要过些时候才能洗出来,而我们不可能一直等下去,商家一般会给我们一张单据,并告知第二天10:00以后凭此单领取照片,而我们就可以暂时离开去做其它事情,等到第二天再带着单据来到摄影店领取照片,如果我们9:30就到了,照片还没有洗出来,我们就会继续等一会儿,直到照片洗出来。以下代码用Future模式实现前述的主线程和广告子线程之间的协作:
主线程:

01public void searchService(query) {
02 
03        FutureAd future = startAdThread();
04 
05        SearchResult result = findResult(query);
06 
07        Ad ad = future.getAd();
08 
09        respond2User(result, ad);
10}
11 
12/* 返回FutureAd对象,类似提货单,主线程稍后通过FutureAd获取准备好的广告数据 */
13FutureAd startAdThread() {
14 
15    FutureAd future = new FutureAd();
16 
17    new AdThread(future).start();
18 
19    return future;
20}

广告线程:

01public class AdThread extends Thread {
02 
03    private FutureAd future;
04 
05    public AdThread(FutureAd ad) {
06 
07        this.future = future;
08    }
09 
10    public void run() {
11 
12        try {
13 
14            Ad ad = retrieveAdFromRemoteServer(); //耗时操作
15 
16            future.setAd(ad);
17 
18        catch (Exception e) {}
19    }
20    //省略部分方法
21}

关键部分是FutureAd对象:

01public class FutureAd {
02 
03    private Ad ad;
04 
05    private boolean done;
06 
07    public FutureAd () {}
08 
09    public synchronized Ad getAd() {
10 
11        while (!done) {  //若广告线程未完成,主线程执行到get方法会阻塞
12 
13            try {
14 
15                wait();
16 
17            catch (InterruptedException e) {}
18        }
19 
20        return ad;
21    }
22 
23    public synchronized void setAd(Ad ad) {
24 
25        this.ad = ad;
26 
27        this.done = true;
28 
29        notify();  //执行完毕,通知主线程
30    }
31}

在Java 5.0以后,Java并发框架(java.util.concurrent)已经内置了Future模式。利用java.util.concurrent包提供的ExecuteService和Future等相关实现使用Future模式,无疑是一种很好的选择,避免了从头开始开发该模式的成本及可能遇到的种种问题。以下利用该框架提供的API改写前面的代码:

01ExecutorService exec = Executors.newCachedThreadPool();
02 
03public void searchService(query) {
04 
05    Future<Ad> future = exec.submit(new Callable<Ad>() {
06 
07        public Ad call() {
08 
09            Ad ad = retrieveAdFromRemoteServer();
10 
11            return ad;
12        }
13    });
14 
15    SearchResult result = findResult(query);
16 
17    //Ad ad = future.get(100, TimeUnit.MILLISECONDS); //此调用最多等待100ms
18    Ad ad = future.get(); 
19 
20    respond2User(result, ad);
21}

与Future模式相关的一个多线程设计问题是忙等(busy wait)。仍以前述搜索主线程和广告线程为例,下列代码演示了忙等。
主线程:

01public void searchService(query) {
02 
03    AdThread adThread = new AdThread();
04 
05    adThread.start();
06 
07    SearchResults results = findResults(query);
08 
09    while (true) {
10 
11        if (!adThread.isDone()) { //忙等!!!若子线程未完成,主线程每隔10ms检查一次,直到完成
12 
13            Thread.sleep(10);
14        }
15    }
16 
17    Ad ad = adThread.getAd();
18 
19    mergeResultsAndAd(results, ad);
20 
21    respond2User();
22}

广告线程:

01public class AdThread extends Thread {
02 
03        private Ad ad;
04 
05        private boolean done = false;
06 
07        public void run() {
08 
09            Ad ad = retrieveAdFromRemoteServer();
10 
11            this.ad = ad;
12 
13            this.done = true;
14 
15        }
16 
17        public boolean isDone() {
18 
19            return this.done;
20        }
21    }
22}

这样的代码在生产环境中并不鲜见。忙等至少会造成两个问题:首先,等待的线程会消耗CPU时间,这是没有必要的。再者,等待的线程和被等待的线程会轮流占用CPU,造成不必要的上下文切换(context switch)。这两个问题都会对性能产生影响。此外,在上述代码中,对AdThread类的done实例变量的写操作结果不能保证马上被读操作看见,除非声明成volatile,原因参见java内存模型。
最近在重构老代码的过程中,遇到了很多这样的问题。而忙等的问题可以由Future模式来解决。在Future模式中,通过巧妙的设计来协调线程间通信,避免不必要的上下文切换,改善了性能。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多