怎么用wait、notify巧妙的设计一个Future模式?

我们知道多线程可以实现同时执行多个任务(只是看起来是同时,其实是CPU的时间片切换特别快我们没感觉而已)。

现在假设一个做饭的场景,你没有厨具也没有食材。你可以去网上买一个厨具,但是这段时间,你不需要闲着啊,可以同时去超市买食材。

设想这是两个线程,主线程去买食材,然后开启一个子线程去买厨具。但是,子线程是需要返回一个厨具的。 如果用普通的线程,只有一个Run方法,而Run方法是没有返回值的,这个时候该怎么办呢?

我们就可以用JDK提供的Future模式。在主线程买完食材之后,可以主动去获取子线程的厨具。(本文认为读者了解Future,因此不对Future用法做过多介绍)

代码如下:

public class FutureCook {
    static class Chuju {

    }

    static class Shicai{

    }

    public static void cook(Chuju chuju,Shicai shicai){
        System.out.println("最后:烹饪中...");
    }

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        //第一步,网购厨具
        Callable<Chuju> shopping = new Callable<Chuju>(){

            @Override
            public Chuju call() throws Exception {
                System.out.println("第一步:下单");
                System.out.println("第一步:等待送货");
                Thread.sleep(5000); //模拟送货时间
                System.out.println("第一步:快递送到");
                return new Chuju();
            }
        };

        FutureTask<Chuju> task = new FutureTask<Chuju>(shopping);
        new Thread(task).start();

        //第二步,购买食材
        Thread.sleep(2000);
        Shicai shicai = new Shicai();
        System.out.println("第二步:食材到位");

        //第三步,烹饪
        if(!task.isDone()){ //是否厨具到位
            System.out.println("第三步:厨具还没到,请等待,也可以取消");
                        //①
//            task.cancel(true);
//            System.out.println("已取消");
//            return;
        }

        //尝试获取结果,如果获取不到,就会进入等待状态
        // 即main线程等待子线程执行结束才能继续往下执行
        Chuju chuju = task.get();
        System.out.println("第三步:厨具到位,可以烹饪了");

        cook(chuju,shicai);

    }
}

返回结果:

第一步:下单
第一步:等待送货
第二步:食材到位
第三步:厨具还没到,请等待,也可以取消
第一步:快递送到
第三步:厨具到位,可以烹饪了
最后:烹饪中...

以上代码表示,子线程购买厨具消耗的时间比较长(假定5秒),而主线程购买食材比较快(2秒),所以我在第三步烹饪之前,先去判断一下买厨具的线程是否执行完毕。此处肯定返回false,然后主线程可以选择继续等待,也可以选择取消。(把①注释打开即可测试取消)

我们可以看到,利用Future模式,可以把原本同步执行的任务改为异步执行,可以充分利用CPU资源,提高效率。

现在,我用wait、notify的方式来实现和以上Future模式一模一样的效果。

大概思想就是,创建一个FutureClient端去发起请求,通过FutureData先立即返回一个结果(此时相当于只返回一个请求成功的通知),然后再去开启一个线程异步地执行任务,获取真实数据RealData。此时,主线程可以继续执行其他任务,当需要数据的时候,就可以调用get方法拿到真实数据。

1)定义一个数据接口,包含获取数据的get方法,判断任务是否执行完毕的isDone方法,和取消任务的cancel方法。

public interface Data<T> {

    T get();

    boolean isDone();

    boolean cancel();
}

2)定义真实数据的类,实现Data接口,用来执行实际的任务和返回真实数据。

public class RealData<T> implements Data<T>{

    private T result ;

    public RealData (){
        this.prepare();
    }

    private void prepare() {
        //准备数据阶段,只有准备完成之后才可以继续往下走
        try {
            System.out.println("第一步:下单");
            System.out.println("第一步:等待送货");
            Thread.sleep(5000);
            System.out.println("第一步:快递送到");
        } catch (InterruptedException e) {
            System.out.println("被中断:"+e);
            //重新设置中断状态
            Thread.currentThread().interrupt();
        }
        Main.Chuju chuju = new Main.Chuju();
        result = (T)chuju;
    }

    @Override
    public T get() {
        return result;
    }

    @Override
    public boolean isDone() {
        return false;
    }

    @Override
    public boolean cancel() {
        return true;
    }

}

prepare方法用来准备数据,其实就是执行的实际任务。get方法用来返回任务的执行结果。

3)定义一个代理类FutureData用于给请求端FutureClient暂时返回一个假数据。等真实数据拿到之后,再装载真实数据。

public class FutureData<T> implements Data<T>{

    private RealData<T> realData ;

    private boolean isReady = false;

    private Thread runningThread;

    public synchronized void setRealData(RealData realData) {
        //如果已经装载完毕了,就直接返回
        if(isReady){
            return;
        }
        //如果没装载,进行装载真实对象
        this.realData = realData;
        isReady = true;
        //进行通知
        notify();
    }

    @Override
    public synchronized T get() {
        //如果没装载好 程序就一直处于阻塞状态
        while(!isReady){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //装载好直接获取数据即可
        return realData.get();
    }

    public boolean isDone() {
        return isReady;
    }

    @Override
    public boolean cancel() {
        if(isReady){
            return false;
        }
        runningThread.interrupt();
        return true;
    }

    public void setRunningThread(){
        runningThread = Thread.currentThread();
    }
}

如果get方法被调用,就会去判断数据是否已经被加载好(即判断isReady的值),如果没有的话就调用wait方法进入等待。

setRealData用于去加载真实的数据,加载完毕之后就把isReady设置为true,然后调用notify方法通知正在等待的线程。此时,get方法收到通知就继续执行,然后返回真实数据realData.get().

另外也简单的实现了一个取消任务的方法cancel,去中断正在执行子任务的线程。

4)FutureClient客户端用于发起请求,异步执行任务。

public class FutureClient {

    public Data call(){
        //创建一个代理对象FutureData,先返回给客户端(无论是否有值)
        final FutureData futureData = new FutureData();
        //启动一个新的线程,去异步加载真实的对象
        new Thread(new Runnable() {
            @Override
            public void run() {
                //此处注意需要记录一下异步加载真实数据的线程,以便后续可以取消任务。
                futureData.setRunningThread();
                RealData realData = new RealData();
                //等真实数据处理完毕之后,把结果赋值给代理对象
                futureData.setRealData(realData);
            }
        }).start();

        return futureData;
    }

}

5)测试

public class Main {

    static class Chuju{

    }

    static class Shicai{

    }

    public static void main(String[] args) throws InterruptedException {

        FutureClient fc = new FutureClient();
        Data data = fc.call();

        Thread.sleep(2000);
        Shicai shicai = new Shicai();
        System.out.println("第二步:食材到位");

        if(!data.isDone()){
            System.out.println("第三步:厨具还没到,请等待或者取消");
            //②
//          data.cancel();
//            System.out.println("已取消");
//          return;
        }

        //真正需要数据的时候,再去获取
        Chuju chuju = (Chuju)data.get();
        System.out.println("第三步:厨具到位,可以烹饪了");

        cook(chuju,shicai);

    }

    public static void cook (Chuju chuju, Shicai shicai){
        System.out.println("最后:烹饪中...");
    }
}

执行结果和用JDK提供的Future模式是一模一样的。我们也可以把②出的代码打开,测试任务取消的结果。

第一步:下单
第一步:等待送货
第二步:食材到位
第三步:厨具还没到,请等待或者取消
已取消
被中断:java.lang.InterruptedException: sleep interrupted

执行取消之后,执行RealData的子线程就会被中断,然后结束任务。

原文地址:https://www.cnblogs.com/starry-skys/p/12354336.html

时间: 2024-10-09 16:29:23

怎么用wait、notify巧妙的设计一个Future模式?的相关文章

线程设计之future模式

Main package com.cmos.ngkm.web.controller.basic; public class Main { public static void main(String[] args) throws InterruptedException{ FutureClient fc = new FutureClient(); Data data = fc.request("请求参数"); System.out.println("请求发送成功!"

模拟MMU设计一个将IPv4地址索引化的路由表,不同于DxR

我不知道有没有人这么玩过,也许有,也许没有.时间和空间永远都在厚此薄彼,只因为设施不全,在资源匮乏的年代,只能取舍.但是如果资源丰盈,鱼 与熊掌,完全可以兼得!对于路由查找而言,紧凑的数据结构占用了很小的空间,难道它就要为此付出时间的代价吗?如果我们考虑MMU设施,就会发现,紧凑的 数据结构不但节省了空间,还提高了速度.       我们长期受到的教育就是取义一定要舍身这样的教育,如果不舍身,取到的不会是义,也可能会被讹诈,不怪自己被讹,只因自己没死.其实仔细想想,即便在资源 不那么丰盈,甚至资

Android 设计一个菱形形状的Imageview组件.

网上没有资料,特来请教下大神 Android 设计一个菱形形状的Imageview组件. >> android 这个答案描述的挺清楚的:http://www.goodpm.net/postreply/android/1010000007107851/Android设计一个菱形形状的Imageview组件.html

设计一个程序能够将某一个目录下面的所有文件名打印出来---File类的使用

,设计一个程序能够将某一个目录下面的所有文件名打印出来 运用到的方法有:返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中的文件和目录:list()           测试此抽象路径名表示的文件是否是一个目录:isDirectory()           返回一个抽象路径名数组,这些路径名表示此抽象路径名表示的目录中的文件:listFiles() package printfilename; import java.io.File; public class PrintFileNam

深度学习:从头设计一个TensorFlow3一样的新一代深度学习系统,到底需要把握哪些要点?

深度学习工具潮流滚滚,各种工具层出不穷.也有各种文章从易用性,可移植性,灵活性和效率方面对于各个系统进行比较.这篇文章希望从系统设计上面来讲来回答这个讨论这个问题:如果想到从头设计一个TensorFlow3一样的新一代深度学习系统,到底需要把握哪些要点. 计算单元:从layer abstraction到operator 大家熟悉的第一代深度学习系统,以cuda-convnet21和caffe为代表.这些系统主要的一大特点是提出了一个以深度学习计算层次layer为基本单元的计算单位.不同的laye

线程池? 如何设计一个动态大小的线程池,有哪些方法?

[线程池?  如何设计一个动态大小的线程池,有哪些方法?] 线程池:顾名思义就是事先创建若干个可执行的线程放入一个池(容器)中, 需要的时候从池中获取线程不用自行创建,使用完毕不需要销毁线程而是放回池中, 从而减少创建和销毁线程对象的开销. 系统启动一个新线程的成本是比较高的,因为它涉及与操作系统的交互.此时,使用线程池可以很好地提高性能,尤其是当程序中需要创建大量生存期很短暂的线程时,更应该考虑使用线程池. 与数据库连接池相似,线程池在系统启动时即创建大量空闲的线程,程序将一个Runnable

设计一个字节数组缓存类

转 http://blog.csdn.net/kakashi8841/article/details/42025367 版权所有,转载须注明出处! 1.为什么要 在做网络通信的时候,经常需要用到: 读:就是我们需要从网络流里面读取字节数据,并且由于分包的原因,我们需要自己缓存这些数据,而不是读完立刻丢掉. 写:我们需要把各种类型的数据变成字节写入.比如把int.string.short等变成字节数组写入流. 2.需要什么 我们需要设计一个类来实现: 支持可以不停地往这个类中添加字节 支持写入in

如何设计一个基于云计算的大型分布式系统

当今云计算被炒的沸沸扬扬,云计算显然已经是软件行业的大势所趋,如何设计一个基于云计算的大型分布式系统呢,下面基于云计算架构大型分布式系统展开鄙人的一点短浅的理解与见识. 首先,整个系统包含若干个系统模块,也就是子系统,每个子系统都单独的运行于一个计算机集群中,每个计算机集群包含若干台计算机. 其次,要设计一个整个系统的核心调度系统,这个调度系统运行在一个单独的计算机集群中,我们姑且将这个计算机集群叫做集群A,而这个核心调度系统只是负责记录系统的核心日志,存储每个子系统的文件位置块与索引,负责调度

设计一个 iOS 控件

代码的等级:可编译.可运行.可测试.可读.可维护.可复用 前言 一个控件从外在特征来说,主要是封装这几点: 交互方式 显示样式 数据使用 对外在特征的封装,能让我们在多种环境下达到 PM 对产品的要求,并且提到代码复用率,使维护工作保持在一个相对较小的范围内:而一个好的控件除了有对外一致的体验之外,还有其内在特征: 灵活性 低耦合 易拓展 易维护 通常特征之间需要做一些取舍,比如灵活性与耦合度,有时候接口越多越能适应各种环境,但是接口越少对外产生的依赖就越少,维护起来也更容易.通常一些前期看起来