Java ConcurrentHashMap损坏值

问题描述:

我有一个偶尔出现奇怪行为的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

我认为你的问题是你的一些操作应该是原子的,而不是。

例如,一个可能的螺纹交织方案如下:

  • 线程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

首先,使用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

首先,甚至不认为存在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