Executor.submit()返回null引发空指针异常的原因与解决咨询
问题分析与解决
为什么会出现Future为null的情况?
首先明确:ExecutorService.submit()方法永远不会返回null。根据Java规范,只要传入的任务(Callable/Runnable)不为null,submit方法一定会返回一个有效的Future实例,用来跟踪任务执行状态。
你遇到的空指针,根源出在ArrayList的线程不安全特性上:
ArrayList不是线程安全集合,当多个线程同时对它进行**写入(add)和读取(遍历)**操作时,会出现并发访问的异常情况。比如在一个线程执行futureList.add(...)的同时,另一个线程正在遍历futureList,此时ArrayList的内部结构可能处于不稳定状态,导致遍历到null元素。- 另外也要排查代码中是否有其他地方直接调用
futureList.add(null),手动往集合里添加了null值,但根据你提供的代码片段,这种可能性较低。
如何解决这个问题?
针对多线程环境下的集合操作,有两种可靠的处理方式:
1. 使用线程安全的集合替换ArrayList
将futureList的实现改为CopyOnWriteArrayList,它是专门为读多写少场景设计的线程安全集合,遍历操作不会受写入操作影响,也不会抛出并发修改异常:
private List<Future<Boolean>> futureList = new CopyOnWriteArrayList<>();
2. 对集合操作加同步锁
如果不想更换集合,可以在所有操作futureList的地方(包括add元素和遍历元素)添加synchronized锁,保证同一时间只有一个线程能操作集合:
// 添加元素时加锁 synchronized (futureList) { futureList.add(crawlerTaskExecutor.submit(() -> fetchDocuments(siteUrl), Boolean.FALSE)); } // 遍历检查时加锁 private boolean isCrawlerRunning() { boolean isRunning = Boolean.FALSE; if (Objects.nonNull(futureList)) { synchronized (futureList) { for (Future<Boolean> future : futureList) { if (future != null && !future.isDone()) { // 额外加null判断做兜底 isRunning = Boolean.TRUE; break; } } } } return isRunning; }
另外,在遍历的时候可以额外增加future != null的判断,作为兜底防护,避免极端情况下的空指针。
内容的提问来源于stack exchange,提问作者Gopal Gupta
相关产品推荐
相关产品推荐

