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

咱们知道多线程能够实现同时执行多个任务(只是看起来是同时,实际上是CPU的时间片切换特别快咱们没感受而已)。java

如今假设一个作饭的场景,你没有厨具也没有食材。你能够去网上买一个厨具,可是这段时间,你不须要闲着啊,能够同时去超市买食材。多线程

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

咱们就能够用JDK提供的Future模式。在主线程买完食材以后,能够主动去获取子线程的厨具。(本文认为读者了解Future,所以不对Future用法作过多介绍)ide

代码以下:测试

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

    }
}

返回结果:this

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

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

咱们能够看到,利用Future模式,能够把本来同步执行的任务改成异步执行,能够充分利用CPU资源,提升效率。代理

如今,我用wait、notify的方式来实现和以上Future模式如出一辙的效果。code

大概思想就是,建立一个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的子线程就会被中断,而后结束任务。

相关文章
相关标签/搜索