2016-08-11 43 views
4

我有一個空指針異常,因爲在列表adPics處有一些空值。它很少發生。這怎麼可能?排序時出現非常奇怪的NullPointerException

(並行此代碼下載圖像,並將它們保存在本地。)

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; 
    } 

Stack trace## Heading ##

有時adPics列表有null值。這是NPE的原因。但是如何?分析線程中執行的代碼,不可能添加null值。如果下載圖像時出現問題,則會引發IOException。 imageNewFileName不能是null

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

+3

(關於問題,而不是downvotes)我想這是因爲你正在添加到來自多個線程的列表,但列表未正確同步。有趣的事情可以發生在你這樣做的時候。 – sstan

+5

您確定'awaitTermination'調用沒有超時,並且您在開始對adPics進行排序的同時向其添加元素?不知道爲什麼會拋出一個NPE,但'ArrayList'不是線程安全的,所以我猜想任何事情都可能發生。 – Tunaki

+0

嗯..謝謝@sstan我會嘗試使用http://stackoverflow.com/questions/11360401/java-synchronized-list –

回答

2

方法awaitTermination不會停止正在運行的線程。它只等到所有線程完成或達到了timeout。因此你的線程仍然將項目添加到你的列表中。

此外,您應該考慮即使達到超時,下載和複製到文件系統仍在運行。

一個簡單但不完美的解決方案是在達到超時時設置一個標誌,並在添加更多項之前檢查標誌。

更好的方法是在達到超時後中斷線程。這應該還包括中斷下載和文件複製。

+0

很有意思。最乾淨的方法是什麼? –

+0

以及關於併發添加成爲問題的人評論?這是兩個問題的混合嗎?他們是獨立的嗎? –

+1

如果您使用同步列表,您將以某種方式僞裝您的問題。它應該避免NPE。但在後臺下載,複製和添加仍然有效。最後,您的列表不會再被分類,因爲新的項目在排序後仍然可以添加。 –

0

您的代碼有幾個問題。保羅已經指出了一個問題。其他問題是您正在同時訪問List adPics。

要解決您的問題而不使用同步列表,您可以創建一個List [CompletableFuture]並在末尾致電CompletableFuture.allOf。 每個CompletableFuture都應返回圖像文件名,但在嘗試下載圖像之前,應檢查時間以確定是否要啓動該操作。如果不是,您可以使用null值完成CompletableFuture。 在CompletableFuture.all中,您可以創建一個不帶空值的新列表並對該列表進行排序。

+0

使用同步列表不會解決問題,只會掩蓋潛在問題並導致其他問題。例如,最終列表沒有確切排序。 –

+0

我同意,這就是我在不使用同步列表的情況下如何解決這個問題的原因。 – CodesInTheDark