并行设计模式(一)-- Future模式

Java多线程编程中,常用的多线程设计模式包括:Future模式、Master-Worker模式、Guarded Suspeionsion模式、不变模式和生产者-消费者模式等。这篇文章主要讲述Future模式,关于其他多线程设计模式的地址如下:
  关于Master-Worker模式的详解: 并行设计模式(二)-- Master-Worker模式
  关于Guarded Suspeionsion模式的详解: 并行设计模式(三)-- Guarded Suspeionsion模式
  关于不变模式的详解: 并行设计模式(四)-- 不变模式
  关于生产者-消费者模式的详解:并行设计模式(五)-- 生产者-消费者模式

  1. Future模式
    Future模式的核心在于:去除了主函数的等待时间,并使得原本需要等待的时间段可以用于处理其他业务逻辑。

Future模式有点类似于商品订单。在网上购物时,提交订单后,在收货的这段时间里无需一直在家里等候,可以先干别的事情。类推到程序设计中时,当提交请求时,期望得到答复时,如果这个答复可能很慢。传统的是一直持续等待直到这个答复收到之后再去做别的事情,但如果利用Future模式,其调用方式改为异步,而原先等待返回的时间段,在主调用函数中,则可以用于处理其他事务。

例如如下的请求调用过程时序图。当call请求发出时,需要很长的时间才能返回。左边的图需要一直等待,等返回数据后才能继续其他操作;而右边的Future模式的图中客户端则无需等到可以做其他的事情。服务器段接收到请求后立即返回结果给客户端,这个结果并不是真实的结果(是虚拟的结果),也就是先获得一个假数据,然后执行其他操作。


图片.png

Future模式的主要参与者如下表所示:


图片.png
  1. Future模式的代码实现


    图片.png

    <1. Main函数的实现

Main函数主要负责调用Client发起请求,并使用返回的数据:

public class Main {
    public static void main(String[] args) {
        Client client = new Client();
        // 这里会立即返回,因为获取的是FutureData,而非RealData
        Data data = client.request("name");
        System.out.println("请求完毕");

        try {
            // 这里可以用一个sleep代替对其他业务逻辑的处理
            // 在处理这些业务逻辑过程中,RealData也正在创建,从而充分了利用等待时间
            Thread.sleep(2000);

            // 使用真实数据
            System.out.println("数据=" + data.getResult());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

<2. Client的实现

Client主要实现了获取futrueData,开启构造RealData的线程,并在接受请求后,很快地返回FutureData

public class Client {
    public Data request(final String string) {
        final FutureData futureData = new FutureData();

        new Thread(new Runnable() {
            @Override
            public void run() {
                // RealData的构建很慢,所以放在单独的线程中运行
                RealData realData = new RealData(string);
                futureData.setRealData(realData);
            }
        }).start();
        return futureData; // 先直接返回FutureData
    }
}

<3. Data的实现

Data是一个接口,提供了getResult()方法。无论futureData或者RealData都实现了这个接口

public interface Data {
    String getResult() throws InterruptedException;
}

<4. FutureData的实现

FutureData实现了一个快速返回的RealData包装。它只是一个包装,或者说是一个RealData的虚拟实现。因此,它可以很快被构造并返回。当使用FutureData的getResult()方法是,程序会阻塞,等待RealData被注入到程序中,才使用RealData的getResult()方法返回。

public class FutureData implements Data {
    RealData realData = null; // FutureData是RealData的封装
    boolean isReady = false; // 是否已经准备好

    public synchronized void setRealData(RealData realData) {
        if (isReady)
            return;
        this.realData = realData;
        isReady = true;
        notifyAll(); // RealData已经被注入到FutureData中了,通知getResult()方法
    }

    @Override
    public String getResult() throws InterruptedException {
        if (!isReady) {
            wait(); // 一直等到RealData注入到FutureData中
        }
        return realData.getResult();
    }
}

<5. RealData的实现

RealData是最终需要使用的数据模型,它的构造很慢。在这里,使用sleep()函数模拟这个过程

public class RealData implements Data {
    protected String data;

    public RealData(String data) {
        // 利用sleep方法来表示RealData构造过程是非常缓慢的
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.data = data;
    }

    @Override
    public String getResult() throws InterruptedException {
        return data;
    }
}
  1. JDK的内置Future模式实现
     由于Future是非常常用的多线程设计模式,因此在JDK中内置了Future模式的实现。这些类在java.util.concurrent包里面。其中最为重要的是FutureTask类,它实现了Runnable接口,作为单独的线程运行。在其run()方法中,通过Sync内部类调用Callable接口,并维护Callable接口的返回对象。当使用FutureTask.get()方法时,将返回Callable接口的返回对象。其核心结构图如下所示:


    图片.png

JDK内置的Future模式功能强大,除了基本的功能外,它还可以取消Future任务,或者设定future任务的超时时间。Callable接口是一个用户自定义的实现。在应用程序中,通过实现Callable接口的call()方法,指定FutureTask的实际工作内容和返回对象。

Future接口提供的线程控制功能有:

1 boolean cancle(boolean mayInterruptIfRunning);  // 取消任务
2 boolean isCancelled();  // 是否已经取消
3 boolean isDone();    // 是否已经完成
4 V get() throws InterruptedException, ExecutionException;  //取得返回对象
5 V get(long timeout, TimeUnit unit);  //取得返回对象,可以设置超时时间

同样,针对上述的实例,如果使用JDK自带的实现,则需要作一些调整。

首先,需要实现Callable接口,实现具体的业务逻辑。在本例中,依然使用RealData来实现这个接口:

 1 public class RealData implements Callable<String> {
 2     private String para;
 3 
 4     public RealData(String para) {
 5         this.para = para;
 6     }
 7 
 8     @Override
 9     public String call() throws Exception {
10         // 利用sleep方法来表示真是业务是非常缓慢的
11         StringBuffer sb = new StringBuffer();
12         for (int i = 0; i < 10; i++) {
13             sb.append(para);
14             try {
15                 Thread.sleep(1000);
16             } catch (InterruptedException e) {
17                 e.printStackTrace();
18             }
19         }
20         return sb.toString();
21     }
22 }

在这个改进中,RealData的构造变动非常快,因为其主要业务逻辑被移动到call()方法内,并通过call()方法返回。

Main方法修改如下,由于使用了JDK的内置框架,Data、FutureData等对象就不再需要了。在Main方法的实现中,直接通过RealData构造FutureTask,并将其作为单独的线程运行。在提交请求后,执行其他业务逻辑,最后通过FutureTask.get()方法,得到RealData的执行结果。

1 public class Main {
 2     public static void main(String[] args) {
 3         FutureTask<String> future = new FutureTask<String>(new RealData("liangyongxing"));
 4         ExecutorService executor = Executors.newFixedThreadPool(1);    // 使用线程池
 5         //执行FutureTask,相当于上例中的client.request("name")发送请求
 6         //在这里开启线程进行RealData的call()执行
 7         executor.submit(future);
 8         System.out.println("请求完毕");
 9 
10         try {
11             // 这里仍然可以做额外的数据操作,这里使用sleep代替其他业务逻辑的处理
12             Thread.sleep(2000);
13             
14             /**
15              * 相当于上例当中的 data.getResult(),取得call()方法的返回值
16              * 如果此时call()方法没有执行完毕,则依然会等待
17              */
18             System.out.println("数据 = " + future.get());
19         } catch (InterruptedException | ExecutionException e) {
20             e.printStackTrace();
21         } finally {
              executor.shutdown();
          }
22     }
23 }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 143,809评论 1 304
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 61,651评论 1 257
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 95,178评论 0 213
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 41,241评论 0 181
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 49,047评论 1 259
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 38,899评论 1 178
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 30,503评论 2 274
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,249评论 0 168
  • 想象着我的养父在大火中拼命挣扎,窒息,最后皮肤化为焦炭。我心中就已经是抑制不住地欢快,这就叫做以其人之道,还治其人...
    爱写小说的胖达阅读 29,125评论 6 235
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 32,605评论 0 213
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,368评论 2 215
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 30,723评论 1 232
  • 白月光回国,霸总把我这个替身辞退。还一脸阴沉的警告我。[不要出现在思思面前, 不然我有一百种方法让你生不如死。]我...
    爱写小说的胖达阅读 24,285评论 0 32
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,190评论 2 214
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 31,634评论 3 209
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,651评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,052评论 0 167
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 33,638评论 2 232
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 33,760评论 2 237

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,003评论 18 139
  • 当我们创建一个线程时,我们想获取线程运行完成后的结果,一般使用回调的方式。例如: 运行结果: 这种方式的实现有三个...
    wo883721阅读 5,673评论 2 9
  • Java-Review-Note——4.多线程 标签: JavaStudy PS:本来是分开三篇的,后来想想还是整...
    coder_pig阅读 1,572评论 2 17
  • 1.官网下载最新的Git(https://git-scm.com/download/): 2.下载好以后安装git...
    给你一颗小瓜子阅读 41,760评论 2 11
  • 总是留不住时光的脚步,蓦然回首,发现,回忆依旧,秋叶早已飘落,翻看那如历史画卷般的相册,照片早已泛黄褪色,但是那里...
    墙头花阅读 151评论 1 2