多线程(10) — Future模式

  Future模式是多线程开发中常用常见的一种设计模式,它的核心思想是异步调用。在调用一个函数方法时候,如果函数执行很慢,我们就要进行等待,但这时我们可能不着急要结果,因此我们可以让被调者立即返回,让它在后台慢慢处理这个请求,对于调用者来说可以先处理一些其他事物,在真正需要数据的场合再去尝试获得需要的数据。对于Future模式来说,虽然它无法立即给出你需要的数据,但是它们返回一个契约给你,将来你可以凭借这个契约去重新获取你需要的信息。主要的角色有:

  • Main:系统启动,调用Client发出请求。
  • Client:返回Data对象,立即返回FutureData,并开启ClientThread线程装配RealData
  • Data:返回数据的接口。
  • FutureData:Future数据构造很快,但是是一个虚拟的数据,需要装配RealData
  • RealData:真实数据,其构造是比较慢的

  Future模式简单实现:一个核心接口Data,就是客户端希望获取的数据,在Future模式中,这个Data接口有两个重要的实现,一个是RealData,是真实数据,就是最终要获得的信息。另外一个是FutureData,它是用来提取RealData的一个订单,因此FutureData可以立即返回。

public interface Data {
    public String getResult();
}

  FutureData实现了一个快速返回的RealData包装,只是一个包装,或者说是虚拟实现,它可以很快构造并返回。当使用FutureData的getResult()方法,如果实际数据没准备好程序会阻塞,等RealData准备好并注入FutureData才最终返回数据。

注意:FutureData是Future模式的关键,实际上是真实数据RealData的代理,封装了获取RealData的等待过程。

public class FutureData implements Data {

    protected RealData realdata = null;
    protected boolean isReady = false;
    
    public synchronized void setRealData(RealData realdata){
        if(isReady){
            return;
        }
        this.realdata = realdata;
        isReady = true;
        notifyAll();
    }
    
    @Override
    public synchronized String getResult() {
        while(!isReady){
            try {
                wait();
            } catch (InterruptedException e) {}
        }
        return realdata.result;
    }
}

  RealData是最终需要使用的数据模型,它的构造很慢,用sleep()函数模拟这个过程,简单地模拟一个字符串的构造。

public class RealData implements Data {

    protected final String result;
    public RealData(String para){
        // RealData的构造可能很慢,需要用户等待很久,这里用sleep模拟
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < 10; i++) {
            sb.append(para);
            try {
                // 这里使用sleep代替一个很慢的操作过程
                Thread.sleep(100);
            } catch (InterruptedException e) {}
        }
        result = sb.toString();
    }
    @Override
    public String getResult() {
        return result;
    }
}

  接下来就是客户端程序,Client主要实现了获取FutureData,并开启构造RealData的线程,并在接受请求后,很快返回FutureData。注意,它不会等待数据真的构造完毕再返回,而是立即返回FutureData,即使这个时候FutureData内没有真实数据。

public class Client {
    public Data request(final String queryStr){
        final FutureData future = new FutureData();
        new Thread(){

            @Override
            public void run() {
                RealData realdata = new RealData(queryStr);
                future.setRealData(realdata);
            }
            
        }.start();
        return future;
    }
}
public static void main(String[] args) {
    Client client = new Client();
    // 这里立即返回,得到的是Future而不是RealData
    Data data = client.request("Hello");
    System.out.println("请求完成");
    try {
        Thread.sleep(2000);// 这个过程代替RealData被创建的过程,也就是自己的业务处理过程
    } catch (InterruptedException e) {}
    // 使用真实的数据
    System.out.println("数据 = "+data.getResult());
}

JDK中的Future模式

  JDK中,Future接口类似于模式中的契约,通过它,可以得到真实的数据。RunnableFuture继承了Future和Runnable俩接口,其中run()方法用于构造真实的数据,它有一个具体的实现FutureTask类,这个实现类有个内部类Sync,一些实质性的工作会委托Sync实现,而Sync类最终会调用Callable接口,完成实际数据的组装工作。Callable接口只有一个方法call(),它会返回需要构造的实际数据。这个Callable接口也是Future框架和应用程序之间的重要接口。要实现自己的业务系统,通常需要实现自己的Callable对象,此外,FutureTask类也是与应用密切相关,通常可以使用Callable实例构造一个FutureTask实例,并将它交给线程池。下面来举个使用的例子:

public class RealData implements Callable<String>{
    private String para;
    public RealDatajdk(String para){
        this.para = para;
    }
    
    @Override
    public String call() throws Exception {
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < 10; i++) {
            sb.append(para);
            Thread.sleep(100);
        }
        return sb.toString();
    }
}

  上述代码实现了Callable接口,它的call()方法会构造我们需要的真实数据并返回,当然这个过程可能是缓慢的,这里使用Thread.sleep()方法模拟它。

public class FutureMain {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        FutureTask<String> future = new FutureTask<String>(new RealData("a"));// 构造FutureTask
        ExecutorService executor = Executors.newFixedThreadPool(1);
        // 执行FutureTask,相当于上例中client.request("a")发送请求
        // 在这里开启线程进行RealData的call()方法执行
        executor.submit(future);
        System.out.println("请求完毕");
        try {
            // 这里可以做额外的数据操作,使用sleep代替其他业务逻辑处理
            Thread.sleep(2000);
        } catch (InterruptedException e) {}
        // 取得call()方法的返回值,如果call()方法还没有执行完,就会等待
        System.out.println("数据 = "+future.get());
    }
}

除基本的功能外JDK还为Future接口提供了一些简单的控制方法

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

Guava对Future模式的支持

  JDK自带的简单Future模式,可以使用get()方法得到处理结果,但是这个方法是阻塞的,因此不利于高并发应用。在Guava中增强了Future模式,增加了对模式完成时的回调接口,使得Future完成时可以自动通知应用程序进行后续处理。使用Guava改写上一节中的FutureMain可以得到更好的效果。

public class FutureDemo {
    public static void main(String[] args) throws InterruptedException{
        ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10));
        ListenableFuture<String> task = service.submit(new RealData("x"));
        task.addListener(new Runnable(){
            @Override
            public void run() {
                System.out.print("异步处理成功:");
                try {
                    System.out.println(task.get());
                } catch (Exception e) {}
            }
        },MoreExecutors.directExecutor());
        System.out.println("main task done......");
        Thread.sleep(3000);
    }
}

  MoreExecutors.listeningDecorator()方法将一个普通的线程池包装为一个包含通知功能的Future线程池。第5行将Callable任务提交到线程池中,并得到一个ListenableFuture。与Future相比,ListenableFuture拥有完成时的通知功能,addListener向ListenableFuture中添加回调函数,即当Future执行完成后,执行addListener中第一个参数中代码

猜你喜欢

转载自www.cnblogs.com/wangyongwen/p/11335334.html