【问题标题】:Very odd NullPointerException while sorting排序时非常奇怪的 NullPointerException
【发布时间】:2016-08-11 18:02:50
【问题描述】:

我有一个空指针异常,因为adPics 列表中有一些空值。它很少发生。怎么可能?

(此代码并行下载图像并保存在本地。)

List<String> downloadAdImages(List<String> imagesUrls, final String itemFolder) {
       final List adPics = new ArrayList<>();
       final ExecutorService executor = newFixedThreadPool(20);
       imagesUrls.forEach(
               picUrl -> executor.submit(() -> {
                   try {
                       String imageNewFileName = imagesUrls.indexOf(picUrl) + "." + getExtension(picUrl);
                       String bigPicUrl = picUrl.replace("b.jpg", "ab.jpg"); // big version
                       copyURLToFile(new URL(bigPicUrl), new File(itemFolder, imageNewFileName), 10, 10);
                       adPics.add(imageNewFileName);
                   } catch (IOException ex) {
                       log.log(Level.WARNING, "Could not download image {0} ({1})", new Object[]{picUrl, ex.getMessage()});
                   }
               }));
       executor.shutdown();
       try {
           executor.awaitTermination(15L, MILLISECONDS);
       } catch (InterruptedException ex) {
           log.log(Level.WARNING, "Could not wait for all images downloads");
       }
       Collections.sort(adPics); // null values at list lead to NPE here. How are there null values?
       return adPics;
   }

有时adPics 列表具有null 值。这就是 NPE 的原因。但是怎么做?分析线程中执行的代码,无法添加null值。如果下载图像有问题,它会抛出一个 IOException。 imageNewFileName 不能是 null

此代码是 Java 8,它使用 Apache Commons IO lib。

【问题讨论】:

  • (关于问题,而不是反对票)我想这是因为您要从多个线程添加到列表中,但列表未正确同步。当你这样做时,可能会发生有趣的事情。
  • 您确定awaitTermination 调用没有超时,并且您开始对adPics 进行排序,同时向其中添加元素吗?不知道为什么会抛出 NPE,但 ArrayList 不是线程安全的,所以我想任何事情都可能发生。
  • @LuísSoares 你不会的。只有当前线程被中断时,您才会看到该消息。见docs.oracle.com/javase/8/docs/api/java/util/concurrent/…你不检查awaitTermination的返回值所以你不知道它是否超时。
  • == null 上设置条件断点很容易找出答案,不是吗?
  • 不要发布文本图像,而是将实际文本复制并粘贴到您的问题中。图片不能参与搜索结果,它们对视力受损的用户毫无用处,而且它们的可读性远低于原生浏览器文本。

标签: java multithreading executorservice


【解决方案1】:

awaitTermination 方法不会停止正在运行的线程。它只等到所有线程都完成或达到timeout。因此,您的线程仍在将项目添加到您的列表中。

您还应该考虑到即使达到超时,下载和复制到文件系统也在运行。

一个简单但不完美的解决方案是在超时时设置一个标志,并在添加更多项目之前检查该标志。

更好的方法是在达到超时后中断线程。这还应该包括中断下载和文件复制。

【讨论】:

  • 非常有趣。最干净的方法是什么?
  • 那些评论并发添加是问题的人呢?它是两个问题的混合体吗?他们是独立的吗?
  • 如果您使用同步列表,您会以某种方式掩盖您的问题。它应该避免 NPE。但在后台下载、复制和添加仍然处于活动状态。最后,您的列表将不再确定排序,因为排序后仍然可以添加新项目。
  • 那么.. 中断下载/复制的最佳方法是什么?这甚至有意义吗?或者...您的意思是我应该在添加到列表之前检查经过的时间吗?
  • @sstan 确保从多个线程访问列表应该是同步的 :)
【解决方案2】:

您的代码存在几个问题。保罗已经指出了一个问题。另一个问题是您正在同时访问 List adPics。

要在不使用同步列表的情况下解决您的问题,您可以创建一个 List[CompletableFuture] 并在最后调用 CompletableFuture.allOf。 每个 CompletableFuture 都应该返回图像文件名,但在尝试下载图像之前,它应该检查时间以查看您是否要开始该操作。如果没有,您可以使用 null 值完成 CompletableFuture。 在 CompletableFuture.allOf 中,您可以创建一个没有空值的新列表并对该列表进行排序。

【讨论】:

  • 使用同步列表并不能解决问题,它只会掩盖潜在问题并导致其他问题。例如,最终列表肯定没有排序。
  • 我同意,这就是我提出如何在不使用同步列表的情况下解决它的原因。
猜你喜欢
  • 2017-02-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-26
相关资源
最近更新 更多