13.线程系列- JUC中的Executor详解2

本文的内容

  1. ExecutorCompletionService出现的背景
  2. 介绍CompletionService接口以及常见的方法
  3. 介绍ExecutorCompletionService类以及其原理
  4. 实例:执行一批任务,然后消费执行结果
  5. 实例:异步执行一批任务,有一个完成则立即返回,其他取消

需要解决的问题

举个例子:

买新房了,然后在往下下单买冰箱,洗衣机,电器商家不同,所以送货耗时不一样,然后等他们送货,快递只愿意送到楼下,然后我们自己将其搬到家中。

public class Demo11 {

    static class GoodModel {
        //商品名称
        String goodName;
        //购物时间
        long startTime;
        //送达时间
        long endTime;

        GoodModel(String goodName, long startTime, long endTime) {
            this.goodName = goodName;
            this.startTime = startTime;
            this.endTime = endTime;
        }

        @Override
        public String toString() {
            return "GoodModel{" +
                    "goodName='" + goodName + '\'' +
                    ", startTime=" + startTime +
                    ", endTime=" + endTime +
                    '}';
        }
    }

    //搬到家中的方法
    static void moveGood(GoodModel goodModel) throws InterruptedException {
        //休眠5秒,模拟搬上楼耗时
        TimeUnit.SECONDS.sleep(5);
        System.out.println("将商品搬上楼,商品信息:" + goodModel);
    }

    //下单时间
    static Callable<GoodModel> buyGood(String goodName, long costTime){
        return ()->{
            long startTime = System.currentTimeMillis();
            System.out.println(startTime + "购买" + goodName + "下单!");
            //模拟送货耗时
            try {
                TimeUnit.SECONDS.sleep(costTime);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            long endTime = System.currentTimeMillis();
            System.out.println(startTime + goodName + "送到了!");
            return new GoodModel(goodName, startTime, endTime);
        };
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        long st = System.currentTimeMillis();
        System.out.println(st + "开始购物!");
        //创建一个线程池
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        //异步下单冰箱
        Future<GoodModel> bFuture = executorService.submit(buyGood("冰箱", 5));
        //异步下单洗衣机
        Future<GoodModel> xFuture = executorService.submit(buyGood("洗衣机", 2));

        //快递送到楼下
        GoodModel bGood = bFuture.get();
        //自己搬到家中
        moveGood(bGood);
        //快递送到楼下
        GoodModel xGood = xFuture.get();
        //自己搬到家中
        moveGood(xGood);
        long et = System.currentTimeMillis();
        System.out.println("总耗时:" + (et - st));
    }
}

运行结果:

1608112145216开始购物!
1608112145340购买洗衣机下单!
1608112145340购买冰箱下单!
1608112145340洗衣机送到了!
1608112145340冰箱送到了!
将商品搬上楼,商品信息:GoodModel{goodName='冰箱', startTime=1608112145340, endTime=1608112150341}
将商品搬上楼,商品信息:GoodModel{goodName='洗衣机', startTime=1608112145340, endTime=1608112147341}
总耗时:15125

从输出中我们可以看出几个时间:

  1. 购买冰箱耗时5秒

  2. 购买洗衣机耗时2秒

  3. 将冰箱送上楼耗时5秒

  4. 将洗衣机送上楼耗时5秒

  5. 共计耗时15秒

购买洗衣机、冰箱都是异步执行的,我们先把冰箱送上楼了,然后再把冰箱送上楼了。上面大家应该发现了一个问题,洗衣机先到的,洗衣机到了,我们并没有去把洗衣机送上楼,而是在等待冰箱到货(bxFuture.get();),然后将冰箱送上楼,中间导致浪费了3秒,现实中应该是这样的,先到的先送上楼,修改一下代码,洗衣机先到的,先送洗衣机上楼,代码如下:

public static void main(String[] args) throws ExecutionException, InterruptedException {
        long st = System.currentTimeMillis();
        System.out.println(st + "开始购物!");
        //创建一个线程池
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        //异步下单冰箱
        Future<GoodModel> bFuture = executorService.submit(buyGood("冰箱", 5));
        //异步下单洗衣机
        Future<GoodModel> xFuture = executorService.submit(buyGood("洗衣机", 2));

        //快递送到楼下
        GoodModel xGood = xFuture.get();
        //自己搬到家中
        moveGood(xGood);
        //快递送到楼下
        GoodModel bGood = bFuture.get();
        //自己搬到家中
        moveGood(bGood);
        long et = System.currentTimeMillis();
        System.out.println("总耗时:" + (et - st));
    }

我们只修改了冰箱和洗衣机送到楼下,以及搬到家中的逻辑顺序,其他不变,执行结果:

1608112310548开始购物!
1608112310670购买冰箱下单!
1608112310670购买洗衣机下单!
1608112310670洗衣机送到了!
1608112310670冰箱送到了!
将商品搬上楼,商品信息:GoodModel{goodName='洗衣机', startTime=1608112310670, endTime=1608112312672}
将商品搬上楼,商品信息:GoodModel{goodName='冰箱', startTime=1608112310670, endTime=1608112315672}
总耗时:12125

耗时12秒,比第一种少了3秒

问题来了,上面是我们通过调整代码达到了最优效果,实际上,购买冰箱和洗衣机具体哪个耗时时间长我们是不知道的,怎么办呢,有没有什么解决办法?

CompletionService接口

CompletionService相当于一个执行任务的服务,通过submit把任务给这个服务,服务内部去执行任务,可以通过服务提供的一些方法获取服务中已经完成的任务。我们看几个方法:

//用于向服务中提交有返回结果的任务,并返回Future对象
Future<V> submit(Callable<V> task);

//用户向服务中提交有返回值的任务去执行,并返回Future对象
Future<V> submit(Runnable task, V result);

//从服务中返回并移除一个已经完成的任务,如果获取不到,会一致阻塞到有返回值为止。此方法会响应线程中断。
Future<V> take() throws InterruptedException;

//从服务中返回并移除一个已经完成的任务,如果内部没有已经完成的任务,则返回空,此方法会立即响应。
Future<V> poll();

//尝试在指定的时间内从服务中返回并移除一个已经完成的任务,等待的时间超时还是没有获取到已完成的任务,则返回空。此方法会响应线程中断
Future<V> poll(long timeout, TimeUnit unit) throws InterruptedException;

ExecutorCompletionService类

ExecutorCompletionService类是CompletionService接口的具体时间

说一下内部原理:ExecutoreCompletionService创建的时候会传入一个线程池,调用submit方法传入需要执行的任务,任务由内部的线程池来处理;ExecutorCompletionService内部有个阻塞队列,任意一个任务完成之后,会将任务的执行结果放入阻塞队列中,然后其他线程可以调用take,poll方法从这个阻塞队列中获取一个已经完成的任务,获取任务返回结果的顺序和执行完成先后顺序一致,所以最先完成的任务会先返回。

看一下构造方法:

public ExecutorCompletionService(Executor executor) {
        if (executor == null)
            throw new NullPointerException();
        this.executor = executor;
        this.aes = (executor instanceof AbstractExecutorService) ?
            (AbstractExecutorService) executor : null;
        this.completionQueue = new LinkedBlockingQueue<Future<V>>();
    }

构造方法需要传入一个Executor对象,这个对象表示任务执行器,所有传入的任务会被这个执行器执行。completionQueue用来存储任务结果的阻塞队列,默认采用的是LinkedBolckingQueue,也支持开发自己设置。通过submit传入需要执行的任务,任务执行完成之后,会放入到comPletionQueue中。

使用ExecutorCompletionService解决文章开头的问题

我们只修改main方法中的代码:

public static void main(String[] args) throws ExecutionException, InterruptedException {
        long st = System.currentTimeMillis();
        System.out.println(st + "开始购物!");
        //创建一个线程池
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        //创建一个ExecutorCompletionService对象
        ExecutorCompletionService completionService = new ExecutorCompletionService(executorService);
        //异步下单冰箱
        completionService.submit(buyGood("冰箱", 5));
        //异步下单洗衣机
        completionService.submit(buyGood("洗衣机", 2));
        for (int i = 0; i < 2; i++) {
            Object o = completionService.take().get();
            if(Objects.nonNull(o) && o instanceof GoodModel){
                moveGood((GoodModel) o);
            }
        }
        long et = System.currentTimeMillis();
        System.out.println("总耗时:" + (et - st));
    }

执行结果:

1608113222407开始购物!
1608113222530购买洗衣机下单!
1608113222530购买冰箱下单!
1608113222530洗衣机送到了!
1608113222530冰箱送到了!
将商品搬上楼,商品信息:GoodModel{goodName='洗衣机', startTime=1608113222530, endTime=1608113224530}
将商品搬上楼,商品信息:GoodModel{goodName='冰箱', startTime=1608113222530, endTime=1608113227530}
总耗时:12124

从输出中可以看出和我们希望的结果一致,代码中下单顺序是:冰箱、洗衣机,冰箱送货耗时5秒,洗衣机送货耗时2秒,洗衣机先到的,然后被送上楼了,冰箱后到被送上楼,总共耗时12秒,和期望的方案一样。

执行一批任务,然后消费执行结果

public class Demo13 {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //定义一个线程池
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        //添加执行任务
        List<Callable> list = new ArrayList<>();
        int taskCount = 5;
        for (int i = 0; i < taskCount; i++) {
            long j = i * 2;
            list.add(() -> {
                TimeUnit.SECONDS.sleep(j);
                return j;
            });
        }
        solve(executorService, list, new Consumer() {
            @Override
            public void accept(Object o) {
                System.out.println(o);
            }
        });
        executorService.shutdown();
    }

    static void solve(Executor e, List<Callable> list, Consumer consumer) throws InterruptedException, ExecutionException {
        ExecutorCompletionService ecs = new ExecutorCompletionService(e);
        for (Callable callable : list) {
            ecs.submit(callable);
        }
        int size = list.size();
        for (int i = 0; i < size; i++) {
            Object obj = ecs.take().get();
            if(Objects.nonNull(obj)){
                consumer.accept(obj);
            }
        }
    }
}

执行结果:

0
2
4
6
8

代码中传入了一批任务进行处理,最终将所有处理完成的按任务完成的先后顺序传递给Consumer进行消费了。

异步执行一批任务,有一个完成立即返回,其他取消

方式1

使用ExecutorCompletionService实现

public class Demo14 {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        long startTime = System.currentTimeMillis();
        //定义一个线程池
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        //添加执行任务
        List<Callable<Long>> list = new ArrayList<>();
        int taskCount = 5;
        for (int i = taskCount; i > 0; i--) {
            long j = i * 2;
            String taskName = "任务" + i;
            list.add(() -> {
                TimeUnit.SECONDS.sleep(j);
                System.out.println(taskName + "执行完毕!");
                return j;
            });
        }
        Long integer = invokeAny(executorService, list);
        System.out.println("耗时:" + (System.currentTimeMillis() - startTime) + ",执行结果:" + integer);
        executorService.shutdown();
    }

    static <T> T invokeAny(Executor e, List<Callable<Long>> list) throws InterruptedException, ExecutionException {
        ExecutorCompletionService<T> ecs = new ExecutorCompletionService(e);
        List<Future> futureList = new ArrayList<>();
        for (Callable callable : list) {
            futureList.add(ecs.submit(callable));
        }
        int n = list.size();
        try {
            for (int i = 0; i < n; i++) {
                T o = ecs.take().get();
                if (Objects.nonNull(o)) {
                    return o;
                }
            }
        }finally {
            for (Future future : futureList) {
                future.cancel(true);
            }
        }
        return null;
    }
}

运行结果:

任务1执行完毕!
耗时:2130,执行结果:2

方式2

使用ExecutorServices实现

public class Demo15 {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        long startTime = System.currentTimeMillis();
        //定义一个线程池
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        //添加执行任务
        List<Callable<Long>> list = new ArrayList<>();
        int taskCount = 5;
        for (int i = taskCount; i > 0; i--) {
            long j = i * 2;
            String taskName = "任务" + i;
            list.add(() -> {
                TimeUnit.SECONDS.sleep(j);
                System.out.println(taskName + "执行完毕!");
                return j;
            });
        }
        Long integer = executorService.invokeAny(list);
        System.out.println("耗时:" + (System.currentTimeMillis() - startTime) + ",执行结果:" + integer);
        executorService.shutdown();
    }


}

运行结果:

Connected to the target VM, address: '127.0.0.1:62985', transport: 'socket'
任务1执行完毕!
耗时:2142,执行结果:2

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值