我有一个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;
}
答案 0 :(得分:3)
我认为你的问题是你的一些操作应该是原子的,而不是。
例如,一种可能的线程交错场景如下:
线程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次调用很可能不会返回同样的结果。
答案 1 :(得分: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可能会为你做这件事,但它也使代码更加清晰。当你对ConcurrentHashMap
进行2次通话时,@ isylias会对竞争条件有所了解。例如,而不是:
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);
应该改写如下。没有必要在引入种族条件的put之前删除@assylias。
map.put(name, contents);
你说:
如果reply.length == 15100,则reply.indexOf(“empty”)返回15099。
使用相同的reply
字符串无法做到这一点。我怀疑你正在寻找不同的线程或以其他方式误解输出。同样,不要误以为java.lang.String
中存在错误。
答案 2 :(得分:0)
首先,如果您按顺序从多个线程调用其方法,则使用ConcurrentHashMap
不会保护您。如果您之后调用containsKey
和get
,而另一个线程在其间调用remove
,则结果为null。一定要只调用get并检查null而不是containsKey / get。它在性能方面也更好,因为两种方法几乎都有相同的成本。
其次,奇怪的indexOf调用结果要么是由于编程错误,要么是指向内存损坏。您的应用程序中是否包含任何本机代码?你在getDataFromFileSystem
做什么?我在使用来自多个线程的FileChannel
个对象时发现了内存损坏。