zoukankan      html  css  js  c++  java
  • 怎么用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的子线程就会被中断,然后结束任务。

  • 相关阅读:
    使用变焦摄影镜头的10条经验
    Savage 2:灵魂拷问者Linux即时战略游戏
    在FlashCom中检测摄像头和麦克风的方法
    Asp.net 在线转Flv
    linux的启动加密
    一个命令轻轻松松重新初始化Ubuntu软件包
    Firefox扩展开发学习杂记
    使用 XUL 实现浏览器扩展,第 1 部分: 使用用户界面特性创建一个 Firefox 浏览
    ffmpeg参数解释中文详细
    用mencoder解决ffmpeg转换wmv/asf to flv花屏的问题,并支持rm/rmvb
  • 原文地址:https://www.cnblogs.com/starry-skys/p/12354336.html
Copyright © 2011-2022 走看看