Tell me more ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

I know, I know. It's never a JRE issue. But I've been over this code a lot and I still don't see the issue. It comes from Java Synchronization Not Working as Expected from SO and the poster named "Luke". The interesting section is a synchronized class that has the following try/finally but still managed to have to have insideGetBuffer escape as true.

insideGetBuffer = true;
try {
    ...
} finally {
    insideGetBuffer = false;
}

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. I also hear that running it as a junit test versus as a Java application produces is different as well.

I'm using the following Java on a MacBook Pro running 10.7.4.

java version "1.6.0_31"
Java(TM) SE Runtime Environment (build 1.6.0_31-b04-415-11M3635)
Java HotSpot(TM) 64-Bit Server VM (build 20.6-b01-415, mixed mode)

Here's the code. It fails for me whether it is run inside of Eclipse or from the command line. Any idea where the error is? Does it work for you? What JRE version are you using? Thanks.

public class RaceConditionTest {

    // 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 RaceConditionTest().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 {
            // these don't have to be volatile, accessed only in synchronized methods
        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;
        }
    }
}
share|improve this question
Thanks for posting this here @Gray. Interestingly enough, when I run the JUnit test, I have to run it many (like 20-25) times before it fails. However, when I run the main, it fails every time with ~412k errors. I'm on 1.6.0_24 64-bit on Windows 7 here. – Luke Jun 13 '12 at 18:40
Just a recommendation, try pathfinder and see if it helps? I have not used it myself, but have heard good things about it from my colleagues. – rahul Jun 13 '12 at 18:51
Just an update here, I had some friends that got it to fail on OSX 64-bit with 1.6.0_31 and more failures on Win 7 64-bit with 1.6.0_24. However that same machine Win 7 didn't fail with 1.6.0_16. – Luke Jun 15 '12 at 14:58
@Gray Could you update your example? Several people have mentioned that the variables that are shared between threads should be marked volatile, especially the boolean. I have made all variables (other than NUMBER_TO_USE) volatile and the issue still happens for me. Interestingly, making this change makes the issue happen more reliably for me. – Luke Jun 15 '12 at 15:06
1  
This is not correct @Luke. It does not need to be volatile because it is only changed from within a synchronized method. That already establishes the memory barrier. They are wrong. – Gray Jun 15 '12 at 15:09
show 2 more comments

closed as off topic by Winston Ewert Jun 16 '12 at 16:23

Questions on Code Review Stack Exchange are expected to relate to code review request within the scope defined in the FAQ. Consider editing the question or leaving comments for improvement if you believe the question can be reworded to fit within the scope. Read more about closed questions here.

Browse other questions tagged or ask your own question.