提问者:小点点

Java已损坏的值


我有一个并发HashMap,偶尔会表现出奇怪的行为。

当我的应用程序首次启动时,我从文件系统中读取一个目录,并使用文件名作为键将每个文件的内容加载到并发哈希映射中。有些文件可能是空的,在这种情况下,我将值设置为“空”。

一旦所有文件都被加载,一个工作线程池将等待外部请求。当请求进来时,我调用getData()函数,在那里我检查并发HashMap是否包含键。如果键存在,我获取值并检查值是否为“空”。如果value.包含(“空”),我返回“未找到文件”。否则,返回文件的内容。当键不存在时,我尝试从文件系统加载文件。

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

有时,并发HashMap会返回非空文件的内容(即value.包含("空")==false),但是该行:

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

返回FALSE。我将IF语句分解为两部分:if(回复!=null)if(!回复.包含("空"))。IF语句的第一部分返回TRUE。第二部分返回FALSE。所以我决定打印出变量“回复”,以确定字符串的内容是否确实包含“空”。情况并非如此,即内容不包含字符串“空”。此外,我添加了这一行

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

由于变量回复在我打印出来时不包含字符串“空”,所以我希望indexOf返回-1。但是该函数返回的值大约是字符串的长度,即如果的值的长度==15100,那么的值返回15099。

我每周都会遇到这个问题,每周大约2-3次。此过程每天都会重新启动,因此会定期重新生成Concurrent tHashMap。

有人在使用Java的并发HashMap时看到过这种行为吗?

编辑

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个答案

匿名用户

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

例如,一种可能的线程交织场景如下:

>

  • 线程1在getData方法中读取此行:

    if (map.containsKey(name)) // (1)
    

    结果为假,线程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)

    这并不能解释您正在观察的具体问题,但它说明了这样一个事实,即当您让许多线程在一段代码中并发运行而不同步时,奇怪的事情可能并且确实会发生。

    就目前而言,我找不到对您描述的场景的解释,除非您在测试中多次调用回复=map. get(name),在这种情况下,这两个调用很可能不会返回相同的结果。

  • 匿名用户

    首先,甚至不要认为在并发HashMap中有bug。JDK错误是非常罕见的,即使是有趣的想法也会让你远离正确调试代码。

    我认为你的bug如下。既然你使用包含("空"),如果文件中的行中有单词"空"会发生什么?这不会把事情搞砸吗?

    与其使用包含("空"),不如使用==。将“空”设为私有静态最终字符串,然后您可以对其使用相等。

    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时,它应该被拉到一个静态最终字段。无论如何,Java可能会为你做这件事,但它也使代码更简洁。
  • @assylias在您对Concurrent tHashMap进行2次调用时发现了竞争条件。例如,而不是做:

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

    你说:

    如果对. long==15100,则对.indexOf("空")返回15099。

    对于相同的回复字符串,这是不可能的。我怀疑您正在查看不同的线程或以其他方式误解了输出。同样,不要被愚弄认为java. lang.String中存在错误。

  • 匿名用户

    首先,如果您按顺序从多个线程调用它的方法,使用ConCurrentHashMap并不能保护您。如果您之后调用有键get,而另一个线程调用删除,您将得到一个空结果。请务必只调用get并检查null而不是有键/get。在性能方面也更好,因为这两种方法几乎具有相同的成本。

    其次,奇怪的indexOf调用结果要么是由于编程错误,要么指向内存损坏。您的应用程序中是否涉及任何本机代码?您在getDataFromFileSystem中做什么?我在使用来自多个线程的FileChannel对象时观察到内存损坏。