Future设计模式及ExecutorService中的submit提交
Future设计模式提供一种异步执行方式。提供这样一个场景,我们进行一个查询调用,该调用耗时时间长,因此当前线程不得不一直阻塞,直到拿到结果。这种情况下,就可以使用异步执行的方式,它会在进行调用时直接返回一个“凭证”,当前线程可以在后续某个时刻通过该“凭证”拿到查询结果。
一、Future设计模式实现
图1.1 Future设计模式UML图
1.1 接口定义
为实现异步执行,我们需要将任务,任务结果解耦合。这至少需要三个部分,简单地,我们定义三个接口Future、Callable和FutureService来完成这一过程。
1.1.1 Future接口
Future提供获取执行结果和判断任务是否完成的两个接口,实际上它的实现类是对执行结果的一种封装。
1 public interface Future<T> { 2 3 //返回计算结果 4 T get() throws InterruptedException; 5 6 //任务是否已经执行完成 7 void finished(T result); 8 9 }
1.1.2 Callable接口
Callable主要提供给调用者实现计算逻辑,并返回最终的计算结果。该接口实际作用与Runnable类似,都是为那些其实例可能被另一个线程执行的类设计,是对任务的一种封装。但是Runnable不会返回结果,并且无法抛出经过检查的异常。
1 public interface Callable<T> { 2 3 T call() throws Exception; 4 5 } 6
1.1.3 FutureService接口
FutureService主要用于提交任务,它对Future和Callable进行耦合,将执行结果赋予Future,否则任务和结果就无法产生联系。这里提供两种方式,一种是Callable,另一种是Runnable提交,实际上区别不大。
1 public interface FutureService<T> { 2 3 //提交Runnable任务,则Future.get返回null 4 Future<?> submit(Runnable runnable); 5 6 Future<T> submit(Callable<T> callable); 7 8 }
1.2 程序实现
1.2.1 FutureIml
FutureIml实现了接收任务执行结果的方法和任务结果提取的方法,在get结果时,若任务还未执行结束,该方法陷入阻塞(可比较producer/consumer的实现)。
1 public class FutureIml<T> implements Future<T> { 2 3 private T result; 4 5 private boolean isDone = false; 6 7 private ReentrantLock lock = new ReentrantLock(); 8 9 private Condition condition = lock.newCondition(); 10 11 @Override 12 public T get() throws InterruptedException { 13 lock.lock(); 14 try{ 15 while (!isDone){ 16 condition.await(); 17 } 18 return result; 19 }finally { 20 lock.unlock(); 21 } 22 } 23 24 @Override 25 public void finished(T result) { 26 lock.lock(); 27 try { 28 this.result = result; 29 this.isDone = true; 30 condition.signalAll(); 31 } finally { 32 lock.unlock(); 33 } 34 } 35 }
1.2.2 FutureServiceIml
FutureServiceIml实现了对任务和结果的联系,可以看到,这种异步的实现是基于多线程的,另起一个新线程执行任务,并在任务结束时将结果传给Future。
1 public class FutureServiceIml<T> implements FutureService<T>{ 2 3 private final static String PRIFIX = "FUTURE-"; 4 5 private AtomicInteger integer = new AtomicInteger(); 6 7 private String getName(){ 8 return PRIFIX+integer.getAndIncrement(); 9 } 10 @Override 11 public Future<?> submit(Runnable runnable) { 12 final FutureIml<Void> future = new FutureIml<>(); 13 new Thread(()->{ 14 runnable.run(); 15 future.finished(null); 16 }, getName()).start(); 17 return future; 18 } 19 20 @Override 21 public Future<T> submit(Callable<T> callable) { 22 final FutureIml<T> future = new FutureIml<>(); 23 new Thread(()->{ 24 try { 25 T result = callable.call(); 26 future.finished(result); 27 } catch (Exception e) { 28 e.printStackTrace(); 29 } 30 }, getName()).start(); 31 return future; 32 } 33 }
测试结果:
1 public class FutureTest { 2 public static void main(String[] args) throws InterruptedException { 3 FutureService service = new FutureServiceIml(); 4 Future future = service.submit(new Callable<String>() { 5 @Override 6 public String call() throws Exception { 7 return getString(); 8 } 9 }); 10 11 System.out.println("Not blocked"); 12 13 System.out.println(future.get()); 14 } 15 16 public static String getString() throws InterruptedException { 17 TimeUnit.SECONDS.sleep(3); 18 return "FINISHED"; 19 } 20 }
1.2.3 增加回调机制
Future很明显存在一个弊端,即get方法会阻塞。这就会造成当调用submit后,立刻调用get,则该阻塞与不使用Future模式的情况几乎相同了。这里的解决办法是回调机制,它可以让调用者不再进行显示地通过get方式获得数据而导致阻塞。
Future<T> submit(Callable<T> callable, Callback<T> callback);
我们将回调接口在任务提交时作为参数注入,这个Callback(JDK1.8)主要用来接受并处理任务的计算结果,当提交的任务执行完成之后,会将结果传递给Callback接口进行进一步的执行,这样在提交任务之后不再会因为通过get方法获得结果而陷入阻塞。
二、ExexecutorService中的submit
和们实现的简单的Future模式一样,ExecutorService中也包含三个部分,并且它是在线程池并发框架下应用的,因此还对Runnable接口进行了适配。
2.1 Callable与Future
Callable对任务进行了封装,但Future提供了更丰富的细节,除了可以从中拿到执行结果外,还可以实现对任务的控制部分控制(取消任务)。
1 public interface Callable<V> { 2 V call() throws Exception; 3 } 4 5 public interface Future<V> { 6 7 boolean cancel(boolean mayInterruptIfRunning); 8 9 boolean isCancelled(); 10 11 boolean isDone(); 12 13 V get() throws InterruptedException, ExecutionException; 14 15 V get(long timeout, TimeUnit unit) 16 throws InterruptedException, ExecutionException, TimeoutException; 17 }
2.2 适配器
Callable和Runnable类似,是为那些其实例可能被另一个线程执行的类设计,是对任务的封装。而线程只能接收Runnable任务,在线程池中最终的任务提交接口,execute方法只接收Runnable任务,因此我们需要兼顾Runnable、Callable和Future。
在我们自己的实现中,Callable被放在run方法下运行。在线程池中提供了更严谨的设计,它使用了两个适配器RunnableFuture和RunnableAdapter。
1 public interface RunnableFuture<V> extends Runnable, Future<V> { 2 void run(); 3 }
RunnableFuture是对Runnable接口和Future接口的适配,表示可以被控制状态的Runnable。通过实现这个接口,使得异步任务也可以通过excute方法提交。
1 static final class RunnableAdapter<T> implements Callable<T> { 2 final Runnable task; 3 final T result; 4 RunnableAdapter(Runnable task, T result) { 5 this.task = task; 6 this.result = result; 7 } 8 public T call() { 9 task.run(); 10 return result; 11 } 12 }
RunnableAdapter是对Runnable和Callalbe的适配,实现了Callable接口,并聚合了Runnable对象。这个实现的主要功能是将Runnable转化为可以异步执行的任务,但这种方式Runnable也并不会给出任务执行结果,result值是使用者传入的。(这里将Callable与Runnable进行适配,当然不用适配也可以做到异步执行,但是需要新的类实现该功能,因此很没必要)。
2.3 FutureTask
我们已经了解了Callable、Future以及适配器,现在需要一个类似FutureService,对任务与结果进行整理,这就是FutureTask的主要工作。FutureTask实现了RunnableFuture接口,当execute方法提交该异步任务时,最终执行的是FutureTask类中的run方法。
1 public void run() { 2 if (state != NEW || 3 !UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread())) 4 return; 5 try { 6 Callable<V> c = callable; 7 if (c != null && state == NEW) { 8 V result; 9 boolean ran; 10 try { 11 result = c.call(); 12 ran = true; 13 } catch (Throwable ex) { 14 result = null; 15 ran = false; 16 setException(ex); 17 } 18 if (ran) 19 set(result); 20 } 21 } finally { 22 //在状态设置之前,runner必须为非null,以防止对run()的并发调用 23 runner = null; 24 25 //为防止泄漏中断,必须在清空运行程序后重新读取状态 26 int s = state; 27 if (s >= INTERRUPTING) 28 handlePossibleCancellationInterrupt(s); 29 } 30 }
run方法中的工作与我们自实现的方法类似,主要是在run方法中执行call方法,并把执行结果交给Future。除此之外FutureTask还有一些需要关注的点。
1 private Callable<V> callable; 2 private Object outcome; 3 private volatile Thread runner; 4 private volatile WaitNode waiters;
这四个域分别是任务,结果,执行线程和一个链表。这个WaitNode是用于在多个线程get结果被阻塞时,用于保存这些阻塞的线程,类中还有具体的阻塞唤醒方法,不做赘述。
2.4 整体执行过程
整体执行过程从ExecutorService的submit方法看,它将要执行的异步任务进一步封装进实现了Runnable接口的FutureTask中,使之能被excute提交。通过excete提交后将Future返回,通过Future拿取执行结果。
使用线程池本身就是异步的,但是Runnable接口不能返回结果,通过Future设计模式,也就曲线救国了。
1 public <T> Future<T> submit(Callable<T> task) { 2 if (task == null) throw new NullPointerException(); 3 RunnableFuture<T> ftask = newTaskFor(task); 4 execute(ftask); 5 return ftask; 6 } 7 8 protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) { 9 return new FutureTask<T>(callable); 10 } 11 12 public FutureTask(Callable<V> callable) { 13 if (callable == null) 14 throw new NullPointerException(); 15 this.callable = callable; 16 this.state = NEW; 17 }