Java 同步未按预期工作

标签 java multithreading synchronization thread-safety

我有一个“简单”的 4 类示例,它可靠地显示了多台机器上 java 同步的意外行为。正如您在下面看到的,鉴于 java sychronized 关键字的协定,Broke Synchronization 永远不应该从 TestBuffer 类中打印出来。

这里有 4 个类将重现该问题(至少对我而言)。我对如何修复这个损坏的示例不感兴趣,而是首先为什么会损坏

Sync Issue - Controller.java

Sync Issue - SyncTest.java

Sync Issue - TestBuffer.java

Sync Issue - Tuple3f.java

这是我运行它时得到的输出:

java -cp . SyncTest
Before Adding
Creating a TestBuffer
Before Remove
Broke Synchronization
1365192
Broke Synchronization
1365193
Broke Synchronization
1365194
Broke Synchronization
1365195
Broke Synchronization
1365196
Done

更新: @Gray 有迄今为止最简单的例子。他的例子可以在这里找到:Strange JRC Race Condition

根据我从其他人那里得到的反馈,看起来这个问题可能出现在 Windows 和 OSX 上的 Java 64 位 1.6.0_20-1.6.0_31(不确定更新的 1.6.0)上。没有人能够在 Java 7 上重现该问题。它可能还需要多核机器来重现该问题。

原始问题:

我有一个类提供以下方法:

  • remove - 从列表中删除给定的项目
  • getBuffer - 遍历列表中的所有项目

我已将问题简化为以下 2 个函数,它们都在同一个对象中,并且它们都是 同步的。除非我弄错了,否则永远不应该打印“中断同步”,因为在输入 remove 之前,应该始终将 insideGetBuffer 设置回 false。但是,在我的应用程序中,当我有 1 个线程重复调用 remove 而其他线程重复调用 getBuffer 时,它正在打印“中断同步”。症状是我得到一个 ConcurrentModificationException

另见:

Very strange race condition which looks like a JRE issue

Sun 错误报告:

这已被 Sun 确认为 Java 中的一个错误。它显然在 jdk7u4 中已修复(不知不觉?),但他们没有将修复程序向后移植到 jdk6。 Bug ID: 7176993

最佳答案

我认为您确实在查看 OSR 中的 JVM 错误。使用来自@Gray 的简化程序(稍作修改以打印错误消息)和一些选项来混淆/打印 JIT 编译,您可以看到 JIT 发生了什么。而且,您可以使用一些选项将其控制到可以抑制问题的程度,这为这是一个 JVM 错误提供了很多证据。

运行方式:

java -XX:+PrintCompilation -XX:CompileThreshold=10000 phil.StrangeRaceConditionTest

你会得到一个错误条件(像其他大约 80% 的运行一样)并且编译打印有点像:

 68   1       java.lang.String::hashCode (64 bytes)
 97   2       sun.nio.cs.UTF_8$Decoder::decodeArrayLoop (553 bytes)
104   3       java.math.BigInteger::mulAdd (81 bytes)
106   4       java.math.BigInteger::multiplyToLen (219 bytes)
111   5       java.math.BigInteger::addOne (77 bytes)
113   6       java.math.BigInteger::squareToLen (172 bytes)
114   7       java.math.BigInteger::primitiveLeftShift (79 bytes)
116   1%      java.math.BigInteger::multiplyToLen @ 138 (219 bytes)
121   8       java.math.BigInteger::montReduce (99 bytes)
126   9       sun.security.provider.SHA::implCompress (491 bytes)
138  10       java.lang.String::charAt (33 bytes)
139  11       java.util.ArrayList::ensureCapacity (58 bytes)
139  12       java.util.ArrayList::add (29 bytes)
139   2%      phil.StrangeRaceConditionTest$Buffer::<init> @ 38 (62 bytes)
158  13       java.util.HashMap::indexFor (6 bytes)
159  14       java.util.HashMap::hash (23 bytes)
159  15       java.util.HashMap::get (79 bytes)
159  16       java.lang.Integer::valueOf (32 bytes)
168  17 s     phil.StrangeRaceConditionTest::getBuffer (66 bytes)
168  18 s     phil.StrangeRaceConditionTest::remove (10 bytes)
171  19 s     phil.StrangeRaceConditionTest$Buffer::remove (34 bytes)
172   3%      phil.StrangeRaceConditionTest::strangeRaceConditionTest @ 36 (76 bytes)
ERRORS //my little change
219  15      made not entrant  java.util.HashMap::get (79 bytes)

共有三个 OSR 替换(编译 ID 上带有 % 注释的替换)。我的猜测是第三个,即调用 remove() 的循环,是造成错误的原因。这可以通过位于工作目录中的 .hotspot_compiler 文件从 JIT 中排除,其内容如下:

exclude phil/StrangeRaceConditionTest strangeRaceConditionTest

当你再次运行程序时,你会得到这个输出:

CompilerOracle: exclude phil/StrangeRaceConditionTest.strangeRaceConditionTest
 73   1       java.lang.String::hashCode (64 bytes)
104   2       sun.nio.cs.UTF_8$Decoder::decodeArrayLoop (553 bytes)
110   3       java.math.BigInteger::mulAdd (81 bytes)
113   4       java.math.BigInteger::multiplyToLen (219 bytes)
118   5       java.math.BigInteger::addOne (77 bytes)
120   6       java.math.BigInteger::squareToLen (172 bytes)
121   7       java.math.BigInteger::primitiveLeftShift (79 bytes)
123   1%      java.math.BigInteger::multiplyToLen @ 138 (219 bytes)
128   8       java.math.BigInteger::montReduce (99 bytes)
133   9       sun.security.provider.SHA::implCompress (491 bytes)
145  10       java.lang.String::charAt (33 bytes)
145  11       java.util.ArrayList::ensureCapacity (58 bytes)
146  12       java.util.ArrayList::add (29 bytes)
146   2%      phil.StrangeRaceConditionTest$Buffer::<init> @ 38 (62 bytes)
165  13       java.util.HashMap::indexFor (6 bytes)
165  14       java.util.HashMap::hash (23 bytes)
165  15       java.util.HashMap::get (79 bytes)
166  16       java.lang.Integer::valueOf (32 bytes)
174  17 s     phil.StrangeRaceConditionTest::getBuffer (66 bytes)
174  18 s     phil.StrangeRaceConditionTest::remove (10 bytes)
### Excluding compile: phil.StrangeRaceConditionTest::strangeRaceConditionTest
177  19 s     phil.StrangeRaceConditionTest$Buffer::remove (34 bytes)
324  15      made not entrant  java.util.HashMap::get (79 bytes)

并且问题没有出现(至少在我所做的重复尝试中没有出现)。

此外,如果稍微更改 JVM 选项,可能会导致问题消失。使用以下任一方法都无法出现问题。

java -XX:+PrintCompilation -XX:CompileThreshold=100000 phil.StrangeRaceConditionTest
java -XX:+PrintCompilation -XX:FreqInlineSize=1 phil.StrangeRaceConditionTest

有趣的是,这两个的编译输出仍然显示了删除循环的 OSR。我的猜测(这是一个很大的猜测)是通过编译阈值延迟 JIT 或更改 FreqInlineSize 会导致 OSR 处理在这些情况下发生更改,从而绕过您可能遇到的错误。

here有关 JVM 选项的信息。

herehere有关 -XX:+PrintCompilation 的输出以及如何处理 JIT 所做的事情的信息。

关于Java 同步未按预期工作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10982941/

相关文章:

java - 直接从 Java 类和 GSON 生成笨拙的 JSON 数据模型

java - 为什么时区模式 "OOOO"不显示完整的 GMT+00 :00 offset format?

c++ - 带有 Qt 的 std::thread

multithreading - Groovy 中的线程数限制

java - 同步方法与mysql连接池创建死锁

java - java中可以显式锁定/解锁对象上的隐式同步锁吗

java - 无法使用 Java 将 int 转换为 char

java - 找出读入数据的正确方法

c# - 问题: Multithread bulkinsert

iphone - 重复调用webservice, objective-c