我有一个“简单”的 4 类示例,它可靠地显示了多台机器上 java 同步的意外行为。正如您在下面看到的,鉴于 java sychronized
关键字的协定,Broke Synchronization
永远不应该从 TestBuffer 类中打印出来。
这里有 4 个类将重现该问题(至少对我而言)。我对如何修复这个损坏的示例不感兴趣,而是首先为什么会损坏。
这是我运行它时得到的输出:
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 选项的信息。
见 here和 here有关 -XX:+PrintCompilation 的输出以及如何处理 JIT 所做的事情的信息。
关于Java 同步未按预期工作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10982941/