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

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

设想这是两个线程,主线程去买食材,然后开启一个子线程去买厨具。但是,子线程是需要返回一个厨具的。 如果用普通的线程,只有一个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资源,提高效率。

现在,我用waitnotify的方式来实现和以上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方法进入等待。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpjfpp.html