2012-07-09 69 views
3

我有一個偶爾出現奇怪行爲的ConcurrentHashMap。Java ConcurrentHashMap損壞值

當我的應用第一次啓動時,我從文件系統讀取一個目錄,並將每個文件的內容加載到ConcurrentHashMap中,並使用文件名作爲關鍵字。某些文件可能爲空,在這種情況下,我將該值設置爲「空」。

所有文件加載完成後,工作線程池將等待外部請求。當請求進來時,我調用getData()函數,在這裏我檢查ConcurrentHashMap是否包含密鑰。如果密鑰存在,則獲取該值並檢查該值是否爲「空」。如果value.contains(「空」),我返回「找不到文件」。否則,返回文件的內容。當密鑰不存在時,我嘗試從文件系統加載文件。

private String getData(String name) { 
    String reply = null; 
    if (map.containsKey(name)) { 
     reply = map.get(name); 
    } else { 
     reply = getDataFromFileSystem(name); 
    } 

    if (reply != null && !reply.contains("empty")) { 
     return reply; 
    } 

    return "file not found"; 
} 

有時,ConcurrentHashMap就返回一個非空文件(即value.contains("empty") == false)的內容,但是該行:

if (reply != null && !reply.contains("empty")) 

返回FALSE。我將IF聲明分爲兩部分:if (reply != null)if (!reply.contains("empty"))。 IF語句的第一部分返回TRUE。第二部分返回FALSE。所以我決定打印出變量「reply」,以確定字符串的內容是否確實包含「empty」。這不是這種情況,即內容不包含字符串「空」。此外,我增加了行

int indexOf = reply.indexOf("empty"); 

自變量回復未包含字符串「空」當我打印出來,我期待indexOf返回-1。但函數返回的值大約爲字符串的長度,即if reply.length == 15100,然後reply.indexOf("empty")返回15099.

我每週都會遇到此問題,每週大約2-3次。此過程每天重新啓動,因此定期重新生成ConcurrentHashMap。

有沒有人在使用Java的ConcurrentHashMap時看到過這樣的行爲?

編輯

private String getDataFromFileSystem(String name) { 
    String contents = "empty"; 
    try { 
     File folder = new File(dir); 

     File[] fileList = folder.listFiles(); 
     for (int i = 0; i < fileList.length; i++) { 
      if (fileList[i].isFile() && fileList[i].getName().contains(name)) { 
       String fileName = fileList[i].getAbsolutePath(); 

       FileReader fr = null; 
       BufferedReader br = null; 

       try { 
        fr = new FileReader(fileName); 
        br = new BufferedReader(fr); 
        String sCurrentLine; 
        while ((sCurrentLine = br.readLine()) != null) { 
         contents += sCurrentLine.trim(); 
        } 
        if (contents.equals("")) { 
         contents = "empty"; 
        } 

        return contents; 
       } catch (Exception e) { 
        e.printStackTrace(); 

        if (contents.equals("")) { 
         contents = "empty"; 
        } 
        return contents; 
       } finally { 
        if (fr != null) { 
         try { 
          fr.close(); 
         } catch (Exception e) { 
          e.printStackTrace(); 
         } 
        } 

        if (br != null) { 
         try { 
          br.close(); 
         } catch (Exception e) { 
          e.printStackTrace(); 
         } 
        } 

        if (map.containsKey(name)) { 
         map.remove(name); 
        } 

        map.put(name, contents); 
       } 
      } 
     } 
    } catch (Exception e) { 
     e.printStackTrace(); 

     if (contents.equals("")) { 
      contents = "empty"; 
     } 
     return contents; 
    } 
    return contents; 
} 
+3

我簡直不相信'foo.indexOf(「empty」)'會永遠*返回'foo.length() - 1'爲非空字符串。這意味着'String.indexOf'已經很糟糕了。我不相信'ConcurrentHashMap'或'String'被破壞 - 我強烈懷疑你的代碼是在某處破壞的。 – 2012-07-09 18:52:24

+0

你可以顯示'getDataFromFileSystem(name);'的代碼嗎? – assylias 2012-07-09 18:56:43

+1

是_actual_ getData()方法,還是您重新將它發佈在這裏? – jtahlborn 2012-07-09 19:13:15

回答

3

我認爲你的問題是你的一些操作應該是原子的,而不是。

例如,一個可能的螺紋交織方案如下:

  • 線程1讀取該線路getData方法:

    if (map.containsKey(name)) // (1) 
    
  • 的結果爲假並且線程1進行到

    reply = getDataFromFileSystem(name); // (2) 
    
  • in getDataFromFileSystem,你有下面的代碼:

    if (map.containsKey(name)) { // (3) 
        map.remove(name); // (4) 
    } 
    map.put(name, contents); // (5) 
    
  • 想象,另一個線程(線程2)到達(1),而線程1是(4)(5)之間:名字不在地圖,所以線程2進入(2)再次

現在,這並不說明你所觀察的具體問題,但它說明當你讓許多線程的代碼段不同步併發運行,奇怪的事情可能而且確實發生的事實。

就目前而言,我找不到您描述的場景的解釋,除非您在測試中多次呼叫reply = map.get(name),在這種情況下,很可能2次調用不會返回相同的結果。

+0

感謝幫助assylias。我將更新我的函數以確保線程安全,然後監視行爲。 – 2012-07-11 13:54:23

0

首先,使用ConcurrentHashMap如果你調用從順序多線程的方法並不能保護你。如果您之後致電containsKeyget,並且另一個線索在兩者之間調用remove,則您將得到空結果。一定要調用get和檢查null而不是containsKey/get。性能也更好,因爲兩種方法幾乎都有相同的成本。

其次,奇怪的indexOf調用結果要麼是由於編程錯誤,要麼是指向內存損壞。您的應用程序中是否包含任何本機代碼?你在做什麼getDataFromFileSystem?我在使用來自多個線程的FileChannel對象時觀察到內存損壞。

+0

我的應用程序中沒有任何本地電話。 ** getDataFromFileSystem **現在在我的原始文章中定義。該函數只需使用BufferedFileReader讀取文件即可。 – 2012-07-09 21:12:49

+0

另外,我改變了我訪問地圖的方式 - 而不是先調用containsKey,然後調用get,然後檢查null。感謝您的提示:) – 2012-07-09 21:13:22

2

首先,甚至不認爲存在ConcurrentHashMap的錯誤。 JDK故障非常罕見,甚至有趣的想法會讓您遠離正確調試代碼。

,我認爲你的錯誤如下。由於您使用的是contains("empty")如果文件中的行中有單詞"empty",會發生什麼情況?這不是要搞砸嗎?

而不是使用contains("empty")我會用==的。使「空」爲private static final String然後你可以使用它的平等。

private final static String EMPTY_STRING_REFERENCE = "empty"; 
... 
if (reply != null && reply != EMPTY_STRING_REFERENCE) { 
    return reply; 
} 
... 
String contents = EMPTY_STRING_REFERENCE; 
... 
// really this should be if (contents.isEmpty()) 
if (contents.equals("")) { 
    contents = EMPTY_STRING_REFERENCE; 
} 

這,順便說一句,你應該使用==唯一一次比較字符串。在這種情況下,你想通過引用和而不是內容來測試它,因爲來自文件的行實際上可能包含魔術字符串。

下面是其他一些要點:

  • 一般情況下,當你在你的程序中多個地方使用相同的String,應拉昇到static final場。無論如何,Java可能會爲你做到這一點,但它也使代碼更加清潔。
  • @assylias是當場就有關種族條件下,如果讓2調用ConcurrentHashMap。例如,不要這樣做:

    if (map.containsKey(name)) { 
        reply = map.get(name); 
    } else { 
    

    您應該執行以下操作,以便只執行一項操作。

    reply = map.get(name); 
    if (reply == null) { 
    
  • 在你的代碼做到這一點:

    if (map.containsKey(name)) { 
        map.remove(name); 
    } 
    map.put(name, contents); 
    

    這應該被改寫成以下。在引入競爭條件的提示之前,沒有必要刪除@assylias提到的。

    map.put(name, contents); 
    
  • 你說:

    如果reply.length == 15100,然後reply.indexOf( 「空」)正在恢復15099.

    這是不可能的相同reply字符串。我懷疑你是在看不同的線程或以某種方式誤解輸出。再次,不要被愚蠢地認爲java.lang.String中存在錯誤。

+1

我不建議在EMPTY_STRING上使用==,因爲代碼分析工具會報告錯誤,並且下一個維護開發人員可能被誤導以「修復」它。看看OPs的實現,沒有明顯的理由,如果沒有讀取任何內容,不會返回空字符串。 – Arne 2012-07-10 17:40:20

+0

我不確定代碼分析工具會報告,但也許。但下一個開發人員的觀點是不錯的。我已將其重命名爲_REFERENCE。 – Gray 2012-07-10 17:50:52

+0

雖然我更喜歡參考ID,但它仍然是空字符串的好主意。 – Gray 2012-07-10 17:51:25