簡體   English   中英

Java ConcurrentHashMap損壞了值

[英]Java ConcurrentHashMap corrupt values

我有一個ConcurrentHashMap,偶爾表現出奇怪的行為。

當我的應用程序第一次啟動時,我從文件系統中讀取一個目錄,並使用文件名作為鍵將每個文件的內容加載到ConcurrentHashMap中。 某些文件可能為空,在這種情況下,我將值設置為“空”。

一旦加載了所有文件,工作線程池將等待外部請求。 當請求進來時,我調用getData()函數,在那里我檢查ConcurrentHashMap是否包含密鑰。 如果密鑰存在,我得到值並檢查值是否為“空”。 如果value.contains(“empty”),我返回“找不到文件”。 否則,返回文件的內容。 當密鑰不存在時,我嘗試從文件系統加載文件。

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”。 這不是這種情況,即內容不包含字符串“empty”。 此外,我添加了這條線

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

由於變量回復在我打印出來時不包含字符串“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;
}

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

例如,一種可能的線程交錯場景如下:

  • 線程1在getData方法中讀取此行:

     if (map.containsKey(name)) // (1) 
  • 結果為false並且線程1進入

     reply = getDataFromFileSystem(name); // (2) 
  • 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次調用很可能不會返回同樣的結果。

首先,甚至不要認為ConcurrentHashMap中存在錯誤。 JDK故障是非常罕見的,甚至有趣的想法將使您遠離正確調試代碼。

我認為您的錯誤如下。 由於您使用的是contains("empty") ,如果文件中的行中包含單詞"empty" ,會發生什么? 這不是搞砸了嗎?

我會使用==而不是使用contains("empty") 將“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無論如何都可能會為你做這件事,但它也使代碼更加清晰。
  • 當你對ConcurrentHashMap進行2次調用時,@ iscylias會對競爭條件有所了解。 例如,而不是做:

     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(“empty”)返回15099。

    使用相同的reply字符串無法做到這一點。 我懷疑你正在尋找不同的線程或以其他方式誤解輸出。 同樣,不要誤以為java.lang.String中存在錯誤。

首先,如果您按順序從多個線程調用其方法,則使用ConcurrentHashMap不會保護您。 如果你調用containsKeyget之后get另一個線程調用remove之間你將得到一個null結果。 一定要只調用get並檢查null而不是containsKey / get。 它在性能方面也更好,因為兩種方法幾乎都有相同的成本。

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM