Future设计模式及ExecutorService中的submit提交

Future设计模式提供一种异步执行方式。提供这样一个场景,我们进行一个查询调用,该调用耗时时间长,因此当前线程不得不一直阻塞,直到拿到结果。这种情况下,就可以使用异步执行的方式,它会在进行调用时直接返回一个“凭证”,当前线程可以在后续某个时刻通过该“凭证”拿到查询结果。

一、Future设计模式实现

clip_image001

                                   图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 }
Future

1.1.2 Callable接口

Callable主要提供给调用者实现计算逻辑,并返回最终的计算结果。该接口实际作用与Runnable类似,都是为那些其实例可能被另一个线程执行的类设计,是对任务的一种封装。但是Runnable不会返回结果,并且无法抛出经过检查的异常。

  1 public interface Callable<T> {
  2 
  3     T call() throws Exception;
  4 
  5 }
  6 
Callable

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 }
FutureService

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 }
FutureIml

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 }
FutureServiceIml

测试结果:

  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 }
FutureTest

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 }
Callable And Future

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

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

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方法中的工作与我们自实现的方法类似,主要是在run方法中执行call方法,并把执行结果交给Future。除此之外FutureTask还有一些需要关注的点。

  1 private Callable<V> callable;
  2 private Object outcome;
  3 private volatile Thread runner;
  4 private volatile WaitNode waiters;
field

这四个域分别是任务,结果,执行线程和一个链表。这个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 }
submit
posted @ 2020-12-19 16:52  Aidan_Chen  阅读(942)  评论(0编辑  收藏  举报