FULL PRODUCT VERSION :
java version "1.6.0_24"
Java(TM) SE Runtime Environment (build 1.6.0_24-b07)
Java HotSpot(TM) 64-Bit Server VM (build 19.1-b02, mixed mode)
Others have said it fails on other versions of 6, but not on 7. Appears to be 64-bit only.
ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows [Version 6.1.7601]
EXTRA RELEVANT SYSTEM CONFIGURATION :
Intel Core i7 X980 CPU
A DESCRIPTION OF THE PROBLEM :
In the example, which I provide below, the `synchronized` keyword is used to protect concurrent access to a class. One function sets a boolean to true upon entry and false upon exit. The other function checks that boolean upon entry. Given that both of these functions are `synchronized`, the boolean should never be true when checked in the other function. However that does not appear to be the case.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
The bug is detailed in the stack overflow question here:
http://stackoverflow.com/questions/10982941/java-synchronization-not-working-as-expected
Another user has created a simplified version contained in a single class file that is here:
http://pastebin.com/AF85FRT7
The complete source is also included below.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Given the expected behavior of the `synchronized` keyword, I would expect `errorC` to be 0 (in the example from the pastebin link) when the application is done running.
ACTUAL -
On my machine, when I run the `main` from the pastebin link I get the following:
Exception in thread "main" java.lang.AssertionError: expected:<0> but was:<412601>
at org.junit.Assert.fail(Assert.java:91)
at org.junit.Assert.failNotEquals(Assert.java:645)
at org.junit.Assert.assertEquals(Assert.java:126)
at org.junit.Assert.assertEquals(Assert.java:470)
at org.junit.Assert.assertEquals(Assert.java:454)
at testing.StrangeRaceConditionTest.strangeRaceConditionTest(StrangeRaceConditionTest.java:59)
at testing.StrangeRaceConditionTest.main(StrangeRaceConditionTest.java:33)
REPRODUCIBILITY :
This bug can be reproduced often.
---------- BEGIN SOURCE ----------
package com.mprew.be.service.x;
import static org.junit.Assert.assertEquals;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import org.junit.Test;
/**
* This is an interesting test case that seems to be indicating a JRE issue.
*
* This comes from http://stackoverflow.com/q/10982941/179850 and the poster named "Luke".
*
* I've boiled the code down to its lowest level and added some comments around the changes that can change the error
* count. This is pretty repeatable to me with 4/5 runs generating errors. If you can't get it to fail I'd try
* decreasing the fixed-rate period and increasing the NUMBER_TO_USE constant.
*/
public class StrangeRaceConditionTest {
// if this is decreased it may remove the errors
public static final int NUMBER_TO_USE = 1000000;
private Map<Integer, Buffer> bufferMap = new HashMap<Integer, Buffer>();
public static void main(String[] args) {
new StrangeRaceConditionTest().strangeRaceConditionTest();
}
@Test
public void strangeRaceConditionTest() {
final Buffer buffer = getBuffer();
TimerTask getBufferTask = new TimerTask() {
@Override
public void run() {
buffer.getBuffer();
}
};
// if the period is increased it seems to reduce the errors
new Timer(true).scheduleAtFixedRate(getBufferTask, 0 /* delay ms */, 10 /* period ms */);
for (long i = 0; i < NUMBER_TO_USE; i++) {
// if we inline the remove() method here then no errors
// if we change this to buffer.remove() then no errors
remove();
}
assertEquals(0, buffer.getErrorC());
}
private synchronized void remove() {
Buffer buffer = getBuffer();
buffer.remove();
}
private synchronized Buffer getBuffer() {
// if we remove this whole map nonesense then no errors
Buffer buffer = bufferMap.get(1);
// if this test/else is flipped so it is buffer == null ... then no errors
if (buffer != null) {
// if this line is commented out then no errors
buffer = bufferMap.get(1);
} else {
buffer = new Buffer();
bufferMap.put(1, buffer);
}
return buffer;
}
// moving this out to its own class file doesn't seem to help
private static class Buffer {
private List<Integer> list = new ArrayList<Integer>();
private boolean insideGetBuffer = false;
private int errorC = 0;
public Buffer() {
// initialize the list
for (long i = 0; i < NUMBER_TO_USE; i++) {
list.add(null);
}
}
public synchronized void remove() {
if (insideGetBuffer) {
// adding a System.out.println here makes the problem more repeatable
// System.out.println("How did we get here?");
errorC++;
}
}
public synchronized void getBuffer() {
insideGetBuffer = true;
try {
// if you comment out this sleep then no errors
Thread.sleep(5);
} catch (Exception e) {
e.printStackTrace();
} finally {
insideGetBuffer = false;
}
}
public synchronized int getErrorC() {
return errorC;
}
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
The problem seems to go away if you used synchronized(list){...} instead of synchronizing on `this` or using the function level `synchronized` keyword.