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;
}
我认为你的问题是你的一些操作应该是原子的,而不是。
例如,一个可能的螺纹交织方案如下:
-
线程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次调用不会返回相同的结果。
感谢帮助assylias。我将更新我的函数以确保线程安全,然后监视行为。 – 2012-07-11 13:54:23
首先,使用ConcurrentHashMap
如果你调用从顺序多线程的方法并不能保护你。如果您之后致电containsKey
和get
,并且另一个线索在两者之间调用remove
,则您将得到空结果。一定要调用get和检查null而不是containsKey/get。性能也更好,因为两种方法几乎都有相同的成本。
其次,奇怪的indexOf调用结果要么是由于编程错误,要么是指向内存损坏。您的应用程序中是否包含任何本机代码?你在做什么getDataFromFileSystem
?我在使用来自多个线程的FileChannel
对象时观察到内存损坏。
我的应用程序中没有任何本地电话。 ** getDataFromFileSystem **现在在我的原始文章中定义。该函数只需使用BufferedFileReader读取文件即可。 – 2012-07-09 21:12:49
另外,我改变了我访问地图的方式 - 而不是先调用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
中存在错误。
我简直不相信'foo.indexOf(“empty”)'会永远*返回'foo.length() - 1'为非空字符串。这意味着'String.indexOf'已经很糟糕了。我不相信'ConcurrentHashMap'或'String'被破坏 - 我强烈怀疑你的代码是在某处破坏的。 – 2012-07-09 18:52:24
你可以显示'getDataFromFileSystem(name);'的代码吗? – assylias 2012-07-09 18:56:43
是_actual_ getData()方法,还是您重新将它发布在这里? – jtahlborn 2012-07-09 19:13:15