繁体   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