zoukankan      html  css  js  c++  java
  • 多线程之Future模式

    转载:https://www.jianshu.com/p/949d44f3d9e3

    什么是Future模式

    Future模式是多线程开发中非常常见的一种设计模式。它的核心思想是异步调用。当我们需要调用一个函数方法时。如果这个函数执行很慢,那么我们就要进行等待。但有时候,我们可能并不急着要结果。因此,我们可以让被调用者立即返回,让他在后台慢慢处理这个请求。对于调用者来说,则可以先处理一些其他任务,在真正需要数据的场合再去尝试获取需要的数据。

    用生活中的例子来打个比喻,就像叫外卖。比如在午休之前我们可以提前叫外卖,只需要点好食物,下个单。然后我们可以继续工作。到了中午下班的时候外卖也就到了,然后就可以吃个午餐,再美滋滋的睡个午觉。而如果你在下班的时候才叫外卖,那就只能坐在那里干等着外卖小哥,最后拿到外卖吃完午饭,午休时间也差不多结束了。

    使用Future模式,获取数据的时候无法立即得到需要的数据。而是先拿到一个契约,你可以再将来需要的时候再用这个契约去获取需要的数据,这个契约就好比叫外卖的例子里的外卖订单。

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

    我们可以看一下使用普通模式和用Future模式的时序图。可以看出来普通模式是串行的,在遇到耗时操作的时候只能等待。而Future模式,只是发起了耗时操作,函数立马就返回了,并不会阻塞客户端线程。所以在工作线程执行耗时操作的时候客户端无需等待,可以继续做其他事情,等到需要的时候再向工作线程获取结果:

    Future模式的简单实现

    首先是FutureData,它是只是一个包装类,创建它不需要耗时。在工作线程准备好数据之后可以使用setData方法将数据传入。而客户端线程只需要在需要的时候调用getData方法即可,如果这个时候数据还没有准备好,那么getData方法就会等待,如果已经准备好了就好直接返回。

    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;
        }
    }

    接着是服务端,客户端在向服务端请求数据的时候服务端不会实际去加载数据,它只是创建一个FutureData,然后创建子线程去加载,而它只需要直接返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;
        }
    }

    客户端代码如下,整个程序只需要运行2秒多,但如果不使用Future模式的话就需要三秒了。

    Server server = new Server();
    FutureData<String> futureData = server.getString();
    //先执行其他操作
    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()的区别吗(如果没有看过的读者可以去看一下)?

    execute方法其实是在Executor中定义的,而ExecutorService继承了Executor。它只是简单的提交了一个Runnable给线程池中的线程去调用:

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

    而submit方法是ExecutorService中定义的,它们都会返回一个Future对象。实际上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) :

    它的返回值实际上是Future<Void>,子线程是不会返回数据的。

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

    这个方法是不是很蛋疼,返回的结果在调用的时候已经给出了。如果我一开始就知道结果那我为什么又要发起子线程呢?

    其实不然,这个result可以是一个代理,它不是实际的结果,它只是存储了结果。我这里给出一个例子大家体会一下吧:

    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) :

    这个方法就比较好理解了, Callable.call()方法在子线程中被调用,同时它有返回值,只有将加载的数据直接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();
    }

    一个实际的例子

    比如我们在计算两个List<Integer>中的数的总和的时候就可以用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();
    }

    转载:https://www.cnblogs.com/plxx/p/4574141.html

    下面的可以不看了,上面的讲的比较好。

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

    Futrure模式:对于多线程,如果线程A要等待线程B的结果,那么线程A没必要等待B,直到B有结果,可以先拿到一个未来的Future,等B有结果是再取真实的结果。

     在多线程中经常举的一个例子就是:网络图片的下载,刚开始是通过模糊的图片来代替最后的图片,等下载图片的线程下载完图片后在替换。而在这个过程中可以做一些其他的事情。

     

    首先客户端向服务器请求RealSubject,但是这个资源的创建是非常耗时的,怎么办呢?这种情况下,首先返回Client一个FutureSubject,以满足客户端的需求,于此同时呢,Future会通过另外一个Thread 去构造一个真正的资源,资源准备完毕之后,在给future一个通知。如果客户端急于获取这个真正的资源,那么就会阻塞客户端的其他所有线程,等待资源准备完毕。

    公共数据接口,FutureData和RealData都要实现。

    public interface Data {

          public abstract String getRequest();

    }

    FutureData,当有线程想要获取RealData的时候,程序会被阻塞。等到RealData被注入才会使用getReal()方法。

    public class FurureData implements Data {

     

          public volatile static boolean ISFLAG = false;

          private RealData realData;

     

          public synchronized void setRealData(RealData realData) {

               // 如果已经获取到结果,直接返回

               if (ISFLAG) {

                     return;

               }

               // 如果没有获取到数据,传递真是对象

               this.realData = realData;

               ISFLAG = true;

               // 进行通知

               notify();

          }

     

          @Override

          public synchronized String getRequest() {

               while (!ISFLAG) {

                     try {

                          wait();

                     } catch (Exception e) {

     

                     }

               }

               // 获取到数据,直接返回

               return realData.getRequest();

          }

     

    }

    真实数据RealData

    public class RealData implements Data {

          private String result;

     

          public RealData(String data) {

               System.out.println("正在使用data:" + data + "网络请求数据,耗时操作需要等待.");

               try {

                     Thread.sleep(3000);

               } catch (Exception e) {

     

               }

               System.out.println("操作完毕,获取结果...");

               result = "余胜军";

          }

     

          @Override

          public String getRequest() {

               return result;

          }

    FutureClient  客户端

    public class FutureClient {

     

          public Data request(String queryStr) {

               FurureData furureData = new FurureData();

               new Thread(new Runnable() {

     

                     @Override

                     public void run() {

                          RealData realData = new RealData(queryStr);

                          furureData.setRealData(realData);

                     }

               }).start();

               return furureData;

     

          }

     

    }

    调用者:

    public class Main {

     

          public static void main(String[] args) {

               FutureClient futureClient = new FutureClient();

               Data request = futureClient.request("请求参数.");

               System.out.println("请求发送成功!");

               System.out.println("执行其他任务...");

               String result = request.getRequest();

               System.out.println("获取到结果..." + result);

          }

     

    }

    调用者请求资源,client.request("name"); 完成对数据的准备

    当要获取资源的时候,data.getResult() ,如果资源没有准备好isReady = false;那么就会阻塞该线程。直到资源获取然后该线程被唤醒。

    今天又重新了解了future模式。

    package com.volshell.future2;

    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Future;

    public class FutureTest2 {
    private static class Task implements Callable<String> {
    @Override
    public String call() throws Exception {
    // 模拟真实事务的处理过程,这个过程是非常耗时的。
    Thread.sleep(5000);
    return "call return ";
    }
    }

    public static void main(String[] args) throws InterruptedException,
    ExecutionException {
    List<Future<String>> futures = new ArrayList<Future<String>>();
    ExecutorService executorService = Executors.newCachedThreadPool();

    System.out.println("已经提交资源申请");
    for (int i = 0; i < 10; i++) {
    futures.add(executorService.submit(new Task()));
    }

    for (Future<String> future : futures) {
    // 判断资源是不是已经准备完毕,准备完毕直接获取。
    if (!future.isDone()) {
    System.out.println("资源还没有准备好");
    }
    System.out.println(future.get());
    }
    executorService.shutdown();
    }
    }

    其中的核心就是Callable中的call方法,这个和Runnable中的run 非常类似。

    Runnable和Callable都是接口
    不同之处:
    1.Callable可以返回一个类型V,而Runnable不可以
    2.Callable能够抛出checked exception,而Runnable不可以
    3.Runnable是自从java1.1就有了,而Callable是1.5之后才加上去的
    4.Callable和Runnable都可以应用于executors。而Thread类只支持Runnable.
    上面只是简单的不同,其实这两个接口在用起来差别还是很大的。Callable与executors联合在一起,在任务完成时可立刻获得一个更新了的Future。而Runable却要自己处理

      Future接口,一般都是取回Callable执行的状态用的。其中的主要方法:

    • cancel,取消Callable的执行,当Callable还没有完成时
    • get,获得Callable的返回值
    • isCanceled,判断是否取消了
    • isDone,判断是否完成

    用Executor来构建线程池,应该要做的事:

    1).调用Executors类中的静态方法newCachedThreadPool(必要时创建新 线程,空闲线程会被保留60秒)或newFixedThreadPool(包含固定数量的线程池)等,返回的是一个实现了ExecutorService 接口的ThreadPoolExecutor类或者是一个实现了ScheduledExecutorServiece接口的类对象。

    2).调用submit提交Runnable或Callable对象。

    3).如果想要取消一个任务,或如果提交Callable对象,那就要保存好返回的Future对象。

    4).当不再提交任何任务时,调用shutdown方法

  • 相关阅读:
    更新证书错误Code Sign error: Provisioning profile ‘XXXX'can't be found
    解决Xcode 5下使用SVN出现 The operation couldn’t be completed. (NSURLErrorDomain error -1012.) 问题
    Android模拟器启动不了解决办法
    在AndroidManifest.xml文件中设置Android程序的启动界面方法
    Windows2008+MyEclipse10+Android开发环境搭配
    ADT下载地址整理
    android:inputType常用取值
    VS2010中使用Jquery调用Wcf服务读取数据库记录
    Linux手动安装netcore3.0
    StdOS之运行指示灯
  • 原文地址:https://www.cnblogs.com/fulong133/p/12470097.html
Copyright © 2011-2022 走看看