Luk*_*uke 37 java multithreading synchronization thread-safety
我有一个"简单"的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
Run Code Online (Sandbox Code Playgroud)
更新:@Gray有一个最简单的例子,到目前为止.他的例子可以在这里找到:奇怪的JRC比赛条件
基于我从其他人那里获得的反馈,看起来问题可能发生在Windows 64和OSX上的Java 64位1.6.0_20-1.6.0_31(不确定更新的1.6.0)上.没有人能够在Java 7上重现该问题.它还可能需要多核机器来重现该问题.
原始问题:
我有一个类提供以下方法:
我把问题简化为下面的两个函数,这两个函数都在同一个对象中,它们都是synchronized.除非我弄错了,否则永远不应该打印"Broke Synchronization",因为在输入insideGetBuffer之前应始终将其设置为false remove.但是,在我的应用程序中,当我有1个线程调用重复删除而另一个重复调用getBuffer时,它正在打印"Broke Synchronization".症状是我得到了ConcurrentModificationException.
这被Sun确认为Java中的一个错误.它在jdk7u4中显然是固定的(不知不觉?),但他们没有将修复程序向后移植到jdk6. 错误ID:7176993
phi*_*lwb 17
我认为你确实在查看OSR中的JVM错误.使用@Gray中的简化程序(稍微修改以打印错误消息)和一些混乱/打印JIT编译的选项,您可以看到JIT发生了什么.并且,您可以使用一些选项来控制这个问题,从而可以抑制问题,这为JVM提供了大量证据.
运行方式:
java -XX:+PrintCompilation -XX:CompileThreshold=10000 phil.StrangeRaceConditionTest
Run Code Online (Sandbox Code Playgroud)
你可以得到一个错误条件(像其他大约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)
Run Code Online (Sandbox Code Playgroud)
有三个OSR替换(编译ID上带有%批注的替换).我的猜测是它是第三个,它是调用remove()的循环,负责出错.这可以通过位于工作目录中的.hotspot_compiler文件从JIT中排除,其中包含以下内容:
exclude phil/StrangeRaceConditionTest strangeRaceConditionTest
Run Code Online (Sandbox Code Playgroud)
当您再次运行该程序时,您将获得此输出:
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)
Run Code Online (Sandbox Code Playgroud)
并且问题没有出现(至少不是在我做过的重复尝试中).
此外,如果稍微更改JVM选项,可能会导致问题消失.使用以下任一方法,我无法出现问题.
java -XX:+PrintCompilation -XX:CompileThreshold=100000 phil.StrangeRaceConditionTest
java -XX:+PrintCompilation -XX:FreqInlineSize=1 phil.StrangeRaceConditionTest
Run Code Online (Sandbox Code Playgroud)
有趣的是,这两者的编译输出仍然显示了删除循环的OSR.我的猜测(这是一个很大的问题)是通过编译阈值延迟JIT或更改FreqInlineSize会导致在这些情况下更改OSR处理,从而绕过您可能遇到的错误.
有关JVM选项的信息,请参见此处.
有关-XX:+ PrintCompilation输出的信息以及如何弄乱JIT的功能,请参见此处和此处.
Gra*_*ray 10
因此,根据您发布的代码,Broke Synchronization除非getBuffer()在true和false设置之间抛出异常,否则永远不会打印.请参阅下面的更好的模式.
编辑:
我拿了@Luke的代码并将其削减到这个pastebin类.在我看来,@ Luke正在遇到一个JRE同步错误.我知道很难相信,但我一直在看代码,我只是看不出问题.
既然你提到了ConcurrentModificationException,我怀疑它getBuffer()是在它遍历时抛出它list.您发布的代码不应该抛出ConcurrentModificationException,因为同步的,但我怀疑,一些额外的代码调用add或者remove是不同步的,或当你在整个迭代要删除list.在迭代它时,您可以修改非同步集合的唯一方法是通过以下Iterator.remove()方法:
Iterator<Object> iterator = list.iterator();
while (iterator.hasNext()) {
...
// it is ok to remove from the list this way while iterating
iterator.remove();
}
Run Code Online (Sandbox Code Playgroud)
要保护您的标志,请务必在设置此类关键布尔值时使用try/finally.然后任何异常都会insideGetBuffer适当地恢复:
synchronized public Object getBuffer() {
insideGetBuffer = true;
try {
int i=0;
for(Object item : list) {
i++;
}
} finally {
insideGetBuffer = false;
}
return null;
}
Run Code Online (Sandbox Code Playgroud)
此外,它是一种更好的模式,可以围绕特定对象进行同步,而不是使用方法同步.如果你试图保护list,那么每次在该列表周围添加同步会更好.n
synchronized (list) {
list.remove();
}
Run Code Online (Sandbox Code Playgroud)
您还可以将列表转换为同步列表,synchronize每次都不需要:
List<Object> list = Collections.synchronizedList(new ArrayList<Object>());
Run Code Online (Sandbox Code Playgroud)