invokeAll的原理和遇到的坑

这里记一次工作中遇到的坑.之前没有用过invokeAll这个方法,至于为什么会突然关注,是因为有一个生产bug,同事讨论说很奇怪,没有抛异常,但是结果返回的很出乎意料.
于是就通过arthas来进行排查,由于第一次trace监听错了,以为并没有抛异常,于是进行了一系列错误的分析,感觉不可思议.但是第二天突然意识到是不是自己监听错了,于是进行了重新监听,并且发现其中一条监听记录确实有空指针异常,于是为了确定是不是我的这次调用导致的,又用了watch看参数值,发现确实是因为有一个参数map是空的,后面map.get获取的对象没有判空直接用了,导致了空指针.但是为什么异常没有被抛出来呢???
于是自己写了一个小demo,如下:

@Override
    public void testThread() {
        HashMap<Integer,String> hashMap = Maps.newHashMap();
        Callable<Void> callable = getCallable(hashMap);

        List<Callable<Void>> futureList = Lists.newArrayListWithCapacity(2);
        futureList.add(callable);
        try {
            taskExecutor.invokeAll(futureList, 2*60, TimeUnit.SECONDS);
        } catch (NullPointerException e) {
            log.error("查询数据发生NPE异常", e);
            throw e;
        } catch (AppManufactureException e) {
            log.error("查询数据发生逻辑异常", e);
            throw e;
        } catch (Exception e) {
            log.error("查询数据发生异常", e);
            throw new AppManufactureException(ResponseCodeEnum.FORBIDDEN, "发生错误!", e);
        }
        System.out.println("结束");

    }

线程

private Callable<Void> getCallable(HashMap<Integer,String> hashMap){
        return ()->{
            try {
               test1(hashMap);
                return null;
            }catch (Exception e){
                log.error("填充信息发生异常", e);
                throw e;
            }
        };
    }

抛异常的方法

private void test1(HashMap<Integer,String> hashMap){
		// 因为map没有key为2的键,所以s为空
        String s = hashMap.get(2);
        System.out.println("s" + s);
        if(s.equals("2")){
            hashMap.put(2,s);
        }else {
            hashMap.put(2,"2");
        }
    }

在这里插入图片描述
这里就更能确定,invokeAll把我的异常给吞了,那就一起看看invokeall到底是怎么把异常给吞了的吧.

根据debug来到了AbstractExecutorService.java的invokeAll里边.

public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                         long timeout, TimeUnit unit)
        throws InterruptedException {
        if (tasks == null)
            throw new NullPointerException();
        long nanos = unit.toNanos(timeout);
        // 创建一个list集合
        ArrayList<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());
        boolean done = false;
        try {
            for (Callable<T> t : tasks)
            	// newTaskFor(t):把Callable线程封装成了FutureTask
            	//	protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable){
                //      return new FutureTask<T>(callable);
                // }
            	// 把futureTask放到集合里
                futures.add(newTaskFor(t));

            final long deadline = System.nanoTime() + nanos;
            final int size = futures.size();

            // Interleave time checks and calls to execute in case
            // executor doesn't have any/much parallelism.
            for (int i = 0; i < size; i++) {
            	// 遍历每个线程,并执行线程
                execute((Runnable)futures.get(i));
                nanos = deadline - System.nanoTime();
                if (nanos <= 0L)
                    return futures;
            }

            for (int i = 0; i < size; i++) {
            	// 遍历线程
                Future<T> f = futures.get(i);
                if (!f.isDone()) {
                    if (nanos <= 0L)
                        return futures;
                    try {
                    	// 获取线程执行的结果,
                    	// 这里也能保证所有的线程都执行完才会返回
                    	// 这里的get方法也就是为什么异常被吞的真正的罪魁祸首
                    	// 前面讲到f已经是FutureTask类型了,所以直接找到他的get方法
                        f.get(nanos, TimeUnit.NANOSECONDS);
                    } catch (CancellationException ignore) {
                    } catch (ExecutionException ignore) {
                    } catch (TimeoutException toe) {
                        return futures;
                    }
                    nanos = deadline - System.nanoTime();
                }
            }
            done = true;
            return futures;
        } finally {
            if (!done)
                for (int i = 0, size = futures.size(); i < size; i++)
                    futures.get(i).cancel(true);
        }
    }

FutureTask的get方法

    public V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException {
        if (unit == null)
            throw new NullPointerException();
        // 此时state是0
        int s = state;
        if (s <= COMPLETING &&
            (s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)
            throw new TimeoutException();
        return report(s);
    }

在这里插入图片描述
经过awaitDone方法之后,s就已经变成了3
在这里插入图片描述
在这里插入图片描述
进行了异常转换,直接就抛出throw new ExecutionException((Throwable)x);
在这里插入图片描述
异常就是这样别捕获的.
上面主要就是解释了异常是怎么被吞的,invokeAll的具体执行原理这里就不多说了.

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
`invokeAll` 是 Java Executor 框架提供的一个方法,用于执行一批任务并等待它们全部完成。`invokeAll` 方法接收一个任务列表,返回一个 Future 列表,Future 表示一个异步计算的结果。 `invokeAll` 方法会阻塞当前线程,直到所有任务完成或者超时。如果任意一个任务抛出异常,`invokeAll` 方法会将未完成的任务取消,并抛出 InterruptedException 异常。 下面是一个使用 `invokeAll` 方法的例子: ```java import java.util.ArrayList; import java.util.List; import java.util.concurrent.*; public class Main { public static void main(String[] args) throws InterruptedException { ExecutorService executor = Executors.newFixedThreadPool(3); List<Callable<String>> tasks = new ArrayList<>(); tasks.add(() -> { Thread.sleep(1000); return "Task 1"; }); tasks.add(() -> { Thread.sleep(2000); return "Task 2"; }); tasks.add(() -> { Thread.sleep(3000); return "Task 3"; }); List<Future<String>> results = executor.invokeAll(tasks); for (Future<String> result : results) { try { String value = result.get(); System.out.println(value); } catch (ExecutionException e) { System.out.println("An exception occurred: " + e.getMessage()); } } executor.shutdown(); } } ``` 以上代码中,我们创建了一个包含三个任务的任务列表。每个任务会睡眠一段时间后返回一个字符串结果。我们使用 `invokeAll` 方法将这个任务列表提交到线程池中执行,并获得返回的 Future 列表。在获得结果时,我们使用 `get` 方法来获取异步计算的结果,如果计算过程中抛出了异常,则在 `get` 方法中捕获并处理。最后,我们关闭了线程池。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值