互联网面试请简单介绍高并发场景中的Future设计模式?
凭据管理
在日常生活中,我们经常会遇到一种情况,就是在我们提出需求之后,由于服务者还没有准备好对应的实物交付给我们的时候,就会先给我们一张凭据,等到双方约定的时间到了之后,我们带着这个凭据就可以去获取到我们想要的东西。例如在一个蛋糕店里我们定了一个蛋糕,店长给了我们一个凭据,然后在约定的时间我们去蛋糕店中取走我们定的蛋糕。这样我们不需要在店里等待,而是去完成我们的其他工作。
在程序开发中假设有一个任务执行的时间比较长,通常需要我们等待这个任务完成之后才能进行后续的任务,这个时候服务的调用者就会一直等待。而我们利用上面的介绍的凭据的方式就完全可以解决调用者等待的问题。也就是Java中常说的Future模式。下面我们就来看看关于Java中的Future模式。Future设计模式
Future设计模式是从JDK1.5开始出现的,在我们实现多线程操作的时候有常用的两种方式,一种是实现Runnbale接口,一种是继承Thread类,而我们不是太常用的一种方式就是Call接口的方式,这种方式就是类似于Future模式。其实现类图如下。
下面我们就来看看Future设计模式的具体实现方式。接口逻辑定义
Future接口
根据之前的分析我们的票据应当具有两个功能,一个功能是可以通过它获取到我们想要的结果,另一个功能就是通过它我们可以知道我们想要的蛋糕是否制作完成。如下所示。public interface Future { // 用于返回计算之后的结果 T get() throws InterruptedException; // 用于判断任务是否正常执行 boolean done(); }
FutureService接口
FutureService 的主要作用就是任务的提交,而提交任务的方式有两种,一种是不需要返回值的,一种是需要返回值的。代码如下。public interface FutureService { // 提交不需要进行返回的任务的时候 Future的get方法返回为空 Future<?> submit(Runnable runnable); // 提交需要返回值的任务的时候 Future的get方法就是获取到返回值 Future submit(Tasktask,IN input); // 静态方法获取到实现实例对象 static FutureService newServcie(){ return new FutureServiceImpl<>(); } }
Task接口
有了票据,有了服务人员,接下来就是需要有我们需要完成的任务是什么,而Task就是用来提供给调用者实现计算逻辑的任务。可以接受一个参数并且返回最后的结果。有点类似于Callable接口@FunctionalInterface public interface Task { //给定一个参数经过计算之后得到一个结果 OUT get(IN input); } Future程序实现
Future接口实现
会发现这个接口实现除了实现Future接口的两个方法之外还增加了一个finish的方法,用来完成任务通知操作。public class FutureTask implements Future { // 返回结果 private T result; //任务是否完成 private boolean isDone = false; // 对象锁 private final Object LOCK = new Object(); @Override public T get() throws InterruptedException { synchronized (LOCK){ //当任务还没有完成的时候,调用get方法会被挂起进入阻塞等待 while (!isDone){ LOCK.wait(); } // 返回计算结果 return result; } } protected void finish(T result){ synchronized (LOCK){ //balking 设计模式 if (isDone){ return; } // 计算完成,为result指定结果,并且将isDone设置为true,同时唤起等待中的线程 this.result = result; this.isDone = true; LOCK.notifyAll(); } } @Override public boolean done() { return isDone; } }
在FutureTask中使用了线程间的通信wait和notifyAll,当任务没有完成之前通过get方法获取接口,调用方会进入到阻塞状态,直到任务完成之后收到线程唤醒,finish方法接收到任务完成的通知,然后唤醒因为调用了get方法而进入阻塞的线程。
FutureService实现public class FutureServiceImpl implements FutureService { private final static String FUTURE_THREAD_PREFIX = "FUTURE-"; private final AtomicInteger nextCounter = new AtomicInteger(0); private String getNextName(){ return FUTURE_THREAD_PREFIX+nextCounter.getAndIncrement(); } @Override public Future<?> submit(Runnable runnable) { final FutureTask future = new FutureTask<>(); new Thread(()->{ runnable.run(); // 任务执行结束之后将null作为参数返回 future.finish(null); },getNextName()).start(); return future; } @Override public Future submit(Task task, IN input) { final FutureTask future = new FutureTask<>(); new Thread(()->{ OUT result = task.get(input); // 任务执行结束之后,将真实的结果通过finish的方式传递给future future.finish(result); },getNextName()).start(); return future; } }Future设计模式测试
这里我们提交一个没有返回值的任务,代码如下,public class FutureTest { public static void main(String[] args) throws InterruptedException { FutureService service = FutureService.newServcie(); Future<?> future = service.submit(()->{ try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("任务完成!"); }); future.get(); } }
提交一个有返回值的任务public class FutureTest { public static void main(String[] args) throws InterruptedException { FutureService service = FutureService.newServcie(); Future future = service.submit(input->{ try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } return input.length(); },"Hello"); System.out.println(future.get()); } } 升级获取结果的方法
从上面两个测试的返回结果来看,如果我们调用了future的get方法,那么我们的程序就会进入到阻塞的状态,这个操作与我们的预期不太相符合,这个也是整个的Future模式一直存在的问题。那么我们如何去改进这个获取get方法等待的问题呢?这就引入了一个CallBack的机制。代码如下 @Override public Future submit(Task task, IN input, Callback callback) { final FutureTask future = new FutureTask<>(); new Thread(()->{ OUT result = task.get(input); // 任务执行结束之后,将真实的结果通过finish的方式传递给future future.finish(result); if (null!=callback){ callback.call(result); } },getNextName()).start(); return future; }
测试效果,会发现我们课可以不使用get方法就可以完成执行结果的获取。public class FutureTest { public static void main(String[] args) throws InterruptedException { FutureService service = FutureService.newServcie(); service.submit(input->{ try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } return input.length(); },"Hello",System.out::println); } }总结
在之前的分享中很多的地方我们都提到了Future设计模式,通过这篇文章我们也进一步的了解了关于Future设计模式的思想。其核心思想就是模拟了一个凭据场景,通过这种方式来实现对CPU的高效利用。当然这里我们给出的只是一个基础演示版本,其中的存在的问题还有很多,有兴趣的读者可以自己思考相关内容的优化。