13-Java多线程、线程池框架工具CompletionService

CompletionService

  • CompletionService是JDK为多线程提供的一个工具类。是用来帮助获取线程池中任务的执行结果的,并且他会先获取已经执行完毕的任务的结果,(简单来说就是哪个线程执行完毕了就先返回该线程的结果),因为我们很难知道哪一个线程会先执行完毕,CompletionService帮我们做到了这一点。

一、示例

1.1 代码

/**
 * 任务类是:随机睡眠一段时间来模拟执行任务的耗时,睡眠时间小于1000ms
 * 情况1是普通情况,将任务提交到线程池之后,按照提交任务的顺序依次去除对应任务的结果,最后检查整个过程sleep的时间和总耗时
 * 情况2是使用CompletionService,它不会按照提交任务的顺序去获取结果,而是先获取那些先完成的,在获取后完成的,因此总的耗时
 * 会很接近最慢的那个任务的消耗时间
 * 测试的结果发现使用CompletionService会快一些,而且总耗时比较接近最慢的任务的耗时
 */
public class CompletionServiceTest {

    private final int POOL_SIZE = Runtime.getRuntime().availableProcessors();
    private final int TOTAL_TASK = Runtime.getRuntime().availableProcessors();

    /**
     * 方法一,自己写集合来实现获取线程池中任务的返回结果
     */
    private void testByQueue() throws Exception {
        long start = System.currentTimeMillis();
        //统计所有任务休眠的总时长
        AtomicInteger count = new AtomicInteger(0);
        //1.创建线程池
        ExecutorService pool = Executors.newFixedThreadPool(POOL_SIZE);
        //2.容器存放提交给线程池的任务的结果
        BlockingQueue<Future<Integer>> queue = new LinkedBlockingQueue<Future<Integer>>();

        //3.提交任务
        for (int i = 0; i < TOTAL_TASK; i++) {
            Future<Integer> future = pool.submit(new WorkTask("ExecTask" + i));
            //将结果的Future对象放到队列
            queue.add(future);
        }

        //4.按照提交的顺序,依次阻塞式获取结果
        for (int i = 0; i < TOTAL_TASK; i++) {
            int sleptTime = queue.take().get();
            //打印具体任务的耗时
            System.out.println("Task sleep " + sleptTime + " ms ...");
            count.addAndGet(sleptTime);
        }

        //5.关闭线程池,打印结果
        pool.shutdown();
        System.out.println("Without CompletionService: tasks sleep total  time " + count.get()
                + "ms, and get result spend time is : " + (System.currentTimeMillis() - start) + " ms");
    }

    /**
     * 方法二,通过CompletionService来实现获取线程池中任务的返回结果
     */
    private void testByCompletion() throws Exception {
        long start = System.currentTimeMillis();
        AtomicInteger count = new AtomicInteger(0);
        //1.创建线程池
        ExecutorService pool = Executors.newFixedThreadPool(POOL_SIZE);
        //2.使用CompletionService获取任务的结果
        CompletionService<Integer> cService = new ExecutorCompletionService<>(pool);

        //3.通过CompletionService提交任务
        for (int i = 0; i < TOTAL_TASK; i++) {
            cService.submit(new WorkTask("ExecTask" + i));
        }

        //4.通过CompletionService获取结果
        for (int i = 0; i < TOTAL_TASK; i++) {
            int sleptTime = cService.take().get();
            //打印具体任务的耗时
            System.out.println("Task sleep " + sleptTime + " ms ...");
            count.addAndGet(sleptTime);
        }

        //5.关闭线程池,打印结果
        pool.shutdown();
        System.out.println("CompletionService tasks sleep time " + count.get()
                + "ms,and spend time " + (System.currentTimeMillis() - start) + " ms");
    }

    public static void main(String[] args) throws Exception {
        CompletionServiceTest t = new CompletionServiceTest();
        t.testByQueue();
        t.testByCompletion();
    }
}

public class WorkTask implements Callable<Integer> {
    private String name;

    public WorkTask(String name) {
        this.name = name;
    }

    @Override
    public Integer call() {
        int time = new Random().nextInt(1000);
        try {
            Thread.sleep(time);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return time;
    }
}

1.2 输出

Task sleep 800 ms ...
Task sleep 132 ms ...
Task sleep 667 ms ...
Task sleep 485 ms ...
Task sleep 167 ms ...
Task sleep 911 ms ...
Task sleep 689 ms ...
Task sleep 774 ms ...
Without CompletionService: tasks sleep total  time 4625ms, and get result spend time is : 914 ms
Task sleep 171 ms ...
Task sleep 231 ms ...
Task sleep 240 ms ...
Task sleep 366 ms ...
Task sleep 744 ms ...
Task sleep 779 ms ...
Task sleep 798 ms ...
Task sleep 886 ms ...
CompletionService tasks sleep time 4215ms,and spend time 887 ms

二、源码

  • 从源码上简单看看CompletionService的实现原理

2.1 任务提交

  • cService.submit(new WorkTask(“ExecTask” + i));提交任务的时候,将任务封装为一个RunnableFuture对象,然后再将RunnableFuture封装为一个QueueingFuture对象,
    提交的是这个QueueingFuture对象,也就是线程池执行的是一个QueueingFuture对象的任务;
/**
*ExecutorCompletionService#submit(java.util.concurrent.Callable<V>)
*我们看到,提交任务的时候,将任务封装为一个RunnableFuture对象,然后再将RunnableFuture封装为一个QueueingFuture对象,
*提交的是这个QueueingFuture对象,也就是线程池执行的是一个QueueingFuture对象的任务;
*/
public Future<V> submit(Callable<V> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<V> f = newTaskFor(task);
        executor.execute(new QueueingFuture(f));
        return f;
    }
    

2.2 QueueingFuture

  • QueueingFuture是ExecutorCompletionService的一个内部类,是一个FutureTask的子类,代表具备排队功能的Future(QueueingFuture)。我们看到QueueingFuture的代码很简单,最关键的地方在于done方法里面的completionQueue.add(task);很显然这里将任务的结果放到了一个队列中。
private final BlockingQueue<Future<V>> completionQueue;

private class QueueingFuture extends FutureTask<Void> {
        QueueingFuture(RunnableFuture<V> task) {
            super(task, null);
            this.task = task;
        }
        protected void done() { completionQueue.add(task); }
        private final Future<V> task;
    }

2.3 FutureTask#done

  • 这个方法是继承自FutureTask,在FutureTask中没有任何实,现我们看看,注释还是比较简单的。注释的意思就是,这个方法会在任务被完成的时候调用,默认是什么都不做,子类可以重写该方法来让任务完成的时候调用,在方法内部还可以查询状态来判断任务是否被取消。
/**
     * Protected method invoked when this task transitions to state
     * {@code isDone} (whether normally or via cancellation). The
     * default implementation does nothing.  Subclasses may override
     * this method to invoke completion callbacks or perform
     * bookkeeping. Note that you can query status inside the
     * implementation of this method to determine whether this task
     * has been cancelled.
     */
protected void done() { }

2.4 获取结果

  • 获取结果就是从队列中获取线程执行的结果
public Future<V> take() throws InterruptedException {
        return completionQueue.take();
}

三、小结

  • 到这里我们明白了CompletionService的工作原理:CompletionService的内部包含一个线程池,使用CompletionServicet提交任务的时候,CompletionService将这个任务封装为一个FutureTask提交给线程池执行,这个FutureTask重写了done()这个方法。每个任务执行完毕之后会回调用这个done方法,方法也没做啥特别的,就是将结果放到一个队列里面,因此很自然的先完成的任务就会把结果放在队列的头部了,后完成的就会放在队列尾部。

  • 获取结果的时候,其实就是到队列中去取,因此我们会按照任务执行完毕的先后顺序获取到结果。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值