CompletionService,获取最先完成任务的结果

假设我们要向线程池提交一批任务,并获取任务结果。一般的方式是提交任务后,从线程池得到一批 Future 对象集合,然后依次调用其 get() 方法。

这里有个问题:因为我们会要按固定的顺序来遍历 Future 元素,而 get() 方法又是阻塞的,因此如果某个 Future 对象执行时间太长,会使得我们的遍历过程阻塞在该元素上,无法及时从后面早已完成的 Future 当中取得结果。

CompletionService 解决了这个问题,可以获取先执行完毕的任务。

成员属性

public class ExecutorCompletionService<V> implements CompletionService<V> {
    
    
    private final Executor executor;
    private final AbstractExecutorService aes;
    private final BlockingQueue<Future<V>> completionQueue;
}

CompletionService 的实现原理也是内部维护了一个阻塞队列,当任务执行结束就把任务的执行结果的 Future 对象加入到阻塞队列中。

下面是一个例子

//它本身不包含线程池,创建一个 CompletionService 需要先创建一个 Executor。
CompletionService<Integer> completionService = new ExecutorCompletionService<>(asyncExecutor);
		int sendCount = 0;
		for (Integet item : list) {
    
    
			sendCount++;
			completionService.submit(() -> {
    
    
				return item;
			});

		}
		try {
    
    
			for (int i = 0; i < sendCount; i++) {
    
    
				//阻塞等待最先的返回结果
				completionService.take();
			}
		} catch (InterruptedException e) {
    
    
			logger.error("等待返回结果异常", e);
		}

CompletionService 之所以能够做到这点,是因为它没有采取依次遍历 Future 的方式,而是在中间加上了一个结果队列,任务完成后马上将结果放入队列,那么从队列中取到的就是最早完成的结果。

如果队列为空,那么 take() 方法会阻塞直到队列中出现结果为止。此外 CompletionService 还提供一个 poll() 方法,返回值与 take() 方法一样,不同之处在于它不会阻塞,如果队列为空则立刻返回 null。这算是给用户多一种选择。

take()
取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到BlockingQueue有新的数据被加入;

poll(long timeout, TimeUnit unit)
从BlockingQueue取出一个队首的对象,如果在指定时间内,队列一旦有数据可取,则立即返回队列中的数据。否则知道时间
参数:
timeout - 放弃前等待多长时间,以 unit 为unit
unit – 一个TimeUnit决定如何解释timeout参数
返回值:
此队列的头部,如果在元素可用之前经过了指定的等待时间,则为null

猜你喜欢

转载自blog.csdn.net/Fire_Sky_Ho/article/details/120772028