Java vs C++ – Race Conditions in JVM Languages and C/C++

cjavamultithreading

I was thinking about thread synchronization issues in compiled languages like C++, versus synchronization issues in languages like Java.

I'm wondering how a JVM language like Java doesn't (at least in practice) suffer from coredumps/segfaults/undefined behavior when race conditions occur.

Consider a C++ program with 2 threads. Suppose each thread shares a reference to an std::vector<int>. Each thread continuously loops, and calls std::vector<int>::push_back() – with no synchronization, no locking whatsoever. In this scenario, it's very likely the program will segfault immediately, at least on any mainstream platform/compiler. The reason is obvious: if one thread triggers a reallocation via a call to push_back, and the other thread ends up writing to the old, free'd memory buffer, your program is likely to core dump immediately. Plus, the internal vector size and capacity values may get corrupted, leading to all sorts of undefined behavior and crashing.

But this doesn't seem to happen in Java. Given the same scenario, where you have two threads, each with a reference to some unsynchronized data structure (like an ArrayList<Integer>), and each thread calls ArrayList<Integer>.add(), the worst that ever seems to happen in practice is an exception is thrown, probably an ArrayIndexOutofBounds exception – and of course, the order of insertion is totally random.

I realize the JVM is just executing byte code (except when it's executing JIT'd code), but ultimately that byte code or JIT machine code needs to interact with actual system memory. I assume that Java automatically checks array indices with each access, and the language semantics and garbage collector ensure that all references are non-dangling – but with an unsynchronized data structure, when memory could literally be pulled out from under the program's feet by another thread, at any time, how is the JVM not segfaulting in this scenario unless it's somehow synchronizing memory reads/writes?

Best Answer

Java implements the Java Memory Model which states what should happen in certain situations, and dumping core is not mentioned anywhere in this document. So any Java implementation must take care to implement what the memory model says, and thus throwing core is not permitted. It does sometimes happen, though rarely, but only as a result of bugs in the implementation.

How a particular implementation achieves adherence to the Memory Model rules is left to the implementor - it is only the final behavior which matters. Others have already mentioned that the Garbage Collector plays an important role here and that in particular as long as you have references to an object in any thread, that object can not be freed from memory.