六、CompletionService

六、CompletionService

6.1 JDK中的解释

先看JDK文档对这个接口的解释:

public interface CompletionService<V>

A service that decouples the production of new asynchronous tasks from the consumption of the results 
of completed tasks. Producers submit tasks for execution. Consumers take completed tasks and 
process their results in the order they complete. 

CompletionService类:将异步任务的生产和执行结果的消费“解耦”了。生产者submit (提交)任务;消
费者take(取) 任务,并按任务的完成次序处理结果。

A CompletionService can for example be used to manage asynchronous I/O, in which tasks that perform
 reads are submitted in one part of a program or system, and then acted upon in a different part of the 
 program when the reads complete, possibly in a different order than they were requested.

CompletionService有个场景:在程序中一处提交异步“读”IO任务,在程序另一处等“读”完后再做相应处理,不过处理的顺序就未必按照任务提交的顺序了。

Typically, a CompletionService relies on a separate Executor to actually execute the tasks, 
in which case the CompletionService only manages an internal completion queue. The 
ExecutorCompletionService class provides an implementation of this approach.

CompletionService只是一个接口,具体执行任务还是需要Executor的,CompletionService 其实只是维持
了**一个内部完成队列**(completion queue)。ExecutorCompletionService 是一个CompletionService的实现。

Memory consistency effects: Actions in a thread prior to submitting a task to a CompletionService 
happen-before actions taken by that task, which in turn happen-before actions following a successful 
return from the corresponding take().
内存一致性效应:略

6.2 接口API

再来看CompletionService接口API:
在这里插入图片描述
是不是有种“既视感”?

6.3 ExecutorCompletionService

如前所说,CompletionService只是接口,实际依赖Executor对象完成功能,JDK提供了一个ExecutorCompletionService作为CompletionService的实现,我们实际也是较多使用该类。

6.3.1 构造函数

Constructors  Constructor and Description 

--> ExecutorCompletionService(Executor executor) 
Creates an ExecutorCompletionService using the supplied executor for base task execution and a 
LinkedBlockingQueue as a completion queue. 

--> ExecutorCompletionService(Executor executor, BlockingQueue<Future<V>> completionQueue) 
Creates an ExecutorCompletionService using the supplied executor for base task execution and the 
supplied queue as its completion queue. 

可见:ExecutorCompletionService要使用的话,必须要传入Executor。

6.4 CompletionService & Future

来个Demo:

public class MyCompletionService {

    public static void main(String[] args) {
        // 创建Callable对象列表
        List<MyCallable2> callable2List = IntStream.range(0, 5)
                .mapToObj(u -> new MyCallable2(" callable " + u, 6 - u))
                .collect(Collectors.toList());

        //创建 CompletionService 对象
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5, 10, 1, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
        CompletionService<String> completionService = new ExecutorCompletionService<>(poolExecutor);

        // 生产者提交任务
        callable2List.forEach(completionService::submit);

        // 消费者处理任务结果
        IntStream.range(0, callable2List.size()).forEach(u -> {
            try {
                System.out.println(completionService.take().get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        });
        poolExecutor.shutdown();
    }
}
// output:
/*
可见:先执行完的任务,其任务结果会先被消费掉
callable 4
 callable 3
 callable 2
 callable 1
 callable 0

 */

@Data
@AllArgsConstructor
class MyCallable2 implements Callable<String> {
    private String name;
    private int sleepSeconds;

    @Override
    public String call() throws Exception {
        TimeUnit.SECONDS.sleep(sleepSeconds);
        return name;
    }
}

Demo中,看到输出大家应该明白了:
CompletionService说白了就是内部维护了一个“完成队列”(completion Q),这个Q用来接收执行完的任务的结果。Q具有FIFO的特性,先完成的任务将会先被消费者消费掉,如果任务都被消费完了,还继续 take() 的话,将会阻塞主线程。好了,看下下面这个略加修改的demo,一目了然。

我们回想下上一章的内容,假如我们从List<Future<V>> list里遍历任务执行结果,其实是按遍历顺序,而不是按“先执行完先拿”的原则来获取的;同时 每次get()都是阻塞的,所以遍历list 获取结果并非“best practice”。

public class MyCompletionService2 {

    public static void main(String[] args) {
        // 创建Callable对象列表
        List<MyCallable2> callable2List = IntStream.range(0, 5)
                .mapToObj(u -> new MyCallable2(" callable " + u, 6 - u))
                .collect(Collectors.toList());

        //创建 CompletionService 对象
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5, 10, 1, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
        CompletionService<String> completionService = new ExecutorCompletionService<>(poolExecutor);

        // 生产者提交任务
        callable2List.forEach(completionService::submit);

        // 消费者处理任务结果 :我们把 循环加多了一次
        IntStream.range(0, callable2List.size() + 1).forEach(u -> {
            try {
                //      * Retrieves and removes the Future representing the next
                //     * completed task, waiting if none are yet present.
                // 如果“完成队列”(completion Q)中没有了任务,take()将会一直阻塞,这一点要十分小心
                System.out.println(completionService.take().get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        });
        poolExecutor.shutdown();
    }
}

6.5 take()

这是JDK 中的方法注释,没啥好说的。

public abstract java.util.concurrent.Future<V> take()
                                            throws InterruptedException
Retrieves and removes the Future representing the next completed task, 
waiting if none are yet present.(注意,没元素时会等待,即阻塞)

Returns:
the Future representing the next completed task

6.6 poll()

上面take()会(阻塞地)等待任务结果,可有时我们可能不想“傻傻地等待”,所以有了poll().
看下JDK 注释,无需多言。
poll():若获取不了任务,返回null,而不是"阻塞"(等待)
注意: poll()返回null ,而非poll().get()返回null。所以demo中会快速打印出大量的null!
大家当然就要注意 poll().get()引起的NPE啦!

public Future<V> poll()
Retrieves and removes the Future representing the next completed task, 
or null if none are present. 
Returns: 
the Future representing the next completed task, or null if none are present 

来个demo提神醒脑:

public class MyCompletionService3 {

    public static void main(String[] args) {
        // 创建Callable对象列表
        List<MyCallable2> callable2List = IntStream.range(0, 5)
                .mapToObj(u -> new MyCallable2(" callable " + u, 6 - u))
                .collect(Collectors.toList());

        //创建 CompletionService 对象
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5, 10, 1, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
        CompletionService<String> completionService = new ExecutorCompletionService<>(poolExecutor);

        // 生产者提交任务
        callable2List.forEach(completionService::submit);
        IntStream.range(0, callable2List.size() + 3)
                .forEach(u -> {
                    // ===若获取不到 任务结果,poll()返回null,注意 不是 poll().get()返回null!!!====
//                        System.out.println(completionService.poll().get());
                    System.out.println(completionService.poll());
                });

        poolExecutor.shutdown();
    }
// output:
/*
null
null
null
null
null
null
null
null
 */
    

6.7 poll(timeout, timeUnit)

poll(timeout, timeUnit):带时间限制的poll(),最简单的demo1.
注意:poll(timeout ,timeUnit) --> 调用一次 poll(),在timeout的时间内,只能消费一次“完成队列(completion Q)”中的Future,看demo2。

// demo1
public class MyCompletionService {

    public static void main(String[] args) {
        Callable<String> callableA = () -> {
            TimeUnit.SECONDS.sleep(5);
            return " callable A ";
        };

        Callable<String> callableB = () -> {
            TimeUnit.SECONDS.sleep(10);
            return " callable B ";
        };

        ExecutorService executorService = Executors.newFixedThreadPool(10);
        CompletionService<String> completionService = new ExecutorCompletionService<>(executorService);
        completionService.submit(callableA);
        completionService.submit(callableB);

        System.out.println(DateUtil.currentTime());
        // 在循环中获取任务的执行结果
        IntStream.range(0, 2).forEach(u -> {
            try {
                Future<String> future = completionService.poll(6, TimeUnit.SECONDS);
                System.out.println(future.get());
                 // 假如 poll()的时间设置得很短,极易NPE。注意: 这里的NPE是不会被catch到的!
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }

        });
        executorService.shutdown();
        System.out.println(DateUtil.currentTime());
    }}
/*
2018-11-05 23:31:08
 callable A
 callable B
2018-11-05 23:31:18

 */

// demo2
public class MyCompletionService {

    public static void main(String[] args) {
        Callable<String> callableA = () -> {
            TimeUnit.SECONDS.sleep(5);
            return " callable A ";
        };

        Callable<String> callableB = () -> {
            TimeUnit.SECONDS.sleep(10);
            return " callable B ";
        };

        ExecutorService executorService = Executors.newFixedThreadPool(10);
        CompletionService<String> completionService = new ExecutorCompletionService<>(executorService);
        completionService.submit(callableA);
        completionService.submit(callableB);

        System.out.println(DateUtil.currentTime());
        try {
            System.out.println(" --> " + completionService.poll(4, TimeUnit.SECONDS) + " 等了 4 秒");
            System.out.println(" --> " + completionService.poll(7, TimeUnit.SECONDS).get() + " 又等了 7 秒");
        } catch (Exception e) {
            e.printStackTrace();
        }
        executorService.shutdown();
        System.out.println(DateUtil.currentTime());
    }}
/*
2018-11-05 23:53:41
 --> null 等了 4 秒
 -->  callable A  又等了 7 秒
2018-11-05 23:53:46

注意,这里并没有再去打印 --> callable B 又等了 7 秒。
调用一次poll就只能消费一次future。
 */

6.8 CompletionService & Exception

也没啥好说的,总之就是:Future completionService.take()的结果如果不主动get(),则其中封装的异常不会主动抛到外层。
先来看take() 出现的异常:

public class MyCompletionService2 {

    public static void main(String[] args) {
        Callable<String> callableA = () -> {
            System.out.println(" A  begins " + DateUtil.currentTime());
            Thread.sleep(1000);
            System.out.println(" A  ends " + DateUtil.currentTime());
            return " callable A ";
        };

        Callable<String> callableB = () -> {
            System.out.println(" B begins " + DateUtil.currentTime());
            Thread.sleep(5000);
            int i = 0;
            if (i == 0) {
                throw new Exception(" 抛了个异常! ");
            }
            System.out.println(" B ends " + DateUtil.currentTime());
            return " callable B";
        };

        ExecutorService executorService = Executors.newFixedThreadPool(10);
        ExecutorCompletionService<String> completionService = new ExecutorCompletionService<>(executorService);
        completionService.submit(callableA);
        completionService.submit(callableB);

        try {
            for (int i = 0; i < 2; i++) {
//                下面这样写 --> 没有去 get 整个 completionService.take() 的结果,不会
//                System.out.println(completionService.take().get());
                System.out.println(completionService.take());
                System.out.println("---> " + i);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        executorService.shutdown();
    }
}

再来看 poll()出现的异常:

public class MyCompletionService2 {

    public static void main(String[] args) {
        Callable<String> callableA = () -> {
            System.out.println(" A  begins " + DateUtil.currentTime());
            Thread.sleep(1000);
            System.out.println(" A  ends " + DateUtil.currentTime());
            return " callable A ";
        };

        Callable<String> callableB = () -> {
            System.out.println(" B begins " + DateUtil.currentTime());
            Thread.sleep(5000);
            int i = 0;
            if (i == 0) {
                throw new Exception(" 抛了个异常! ");
            }
            System.out.println(" B ends " + DateUtil.currentTime());
            return " callable B";
        };

        ExecutorService executorService = Executors.newFixedThreadPool(10);
        ExecutorCompletionService<String> completionService = new ExecutorCompletionService<>(executorService);
        completionService.submit(callableA);
        completionService.submit(callableB);

        try {
            for (int i = 0; i < 2; i++) {
                System.out.println("----> " + completionService.poll());
            }
            TimeUnit.SECONDS.sleep(6);
            System.out.println(" ++++++> " + completionService.poll());
            System.out.println(" ++++++> " + completionService.poll());
        } catch (Exception e) {
            e.printStackTrace();
        }

        executorService.shutdown();
    }
}
/* output:
 ----> null
 ----> null
 A  begins 2018-11-15 22:22:18
 B begins 2018-11-15 22:22:18
 A  ends 2018-11-15 22:22:19
 ++++++> java.util.concurrent.FutureTask@2e0fa5d3
 ++++++> java.util.concurrent.FutureTask@5010be6
 
 有没有发现到哪里有点不对? 
 为啥没有
    B ends 2018-11-15 22:22:23
这样的输出!那是因为 B 中抛出了异常中断了正常流程
 */

6.9 Future< V> submit(Runnable task, V result)

ExecutorCompletionService#  Future<V> submit(Runnable task,V result) 和
ExecutorService# <T> Future<T> submit(Runnable task, T result);
两者其实本质相同!用法类似,不再赘述了。

猜你喜欢

转载自blog.csdn.net/qq_30118563/article/details/84112354