Java promises that any variables touched by a data race are still valid, and your program still runs but it offers no guarantees about what value those variables have, so the signed integer you're using to count stuff up from zero might be -16 now, which is astonishing, but your program definitely won't suddenly branch into a re-format disk routine for no reason as it would be allowed to do in C or C++
Go has different rules depending on whether you race a primitive (like int) or some data structure, such as a slice, which has moving parts inside. If you race a data structure you're screwed immediately, this is always Undefined Behaviour. But if you race a primitive, Go says the primitive's representation is now nonsense, and so you're fine if you don't look at it. If you do look at it, and all possible representations are valid (e.g. int in Go is just some bits, all possible bit values are ints, whereas bool not so much) you're still fine but Go makes no promises about what the value is, otherwise that's Undefined Behaviour again.
I don't think Go is really unique here. Java put a lot of work in to deliver the guarantees it has, and since they turned out to be inadequate to reason about programs which don't exhibit Sequential Consistency that was work wasted. Most languages which don't have the data race problem simply don't have concurrency which is, well it's not cheating but it makes them irrelevant. C has "Sequential Consistency" under this constraint too.
> so the signed integer you're using to count stuff up from zero might be -16 now, which is astonishing
Actually, if it is an int, it is guaranteed to not be any number not explicitly set to (java has no-out-of-thin-air guarantees for 32-bit primitives). In practice on every modern implementation it is true of 64-bit primitives as well.
So the prototypical data race condition of incrementing a primitive counter from n threads can loose counts, but will never have any value outside the 0..TRUE_COUNT range.
Ooh, I did not know this. Do you happen to know where the "no-out-of-thin-air" guarantee is for the 32-bit primitives? Presumably in the Memory model docs somewhere?
Java code actually consciously tolerates data races for performance reasons, the prototypical example being the implementation of String#hashCode() (racy single-check idiom).
Go has different rules depending on whether you race a primitive (like int) or some data structure, such as a slice, which has moving parts inside. If you race a data structure you're screwed immediately, this is always Undefined Behaviour. But if you race a primitive, Go says the primitive's representation is now nonsense, and so you're fine if you don't look at it. If you do look at it, and all possible representations are valid (e.g. int in Go is just some bits, all possible bit values are ints, whereas bool not so much) you're still fine but Go makes no promises about what the value is, otherwise that's Undefined Behaviour again.
I don't think Go is really unique here. Java put a lot of work in to deliver the guarantees it has, and since they turned out to be inadequate to reason about programs which don't exhibit Sequential Consistency that was work wasted. Most languages which don't have the data race problem simply don't have concurrency which is, well it's not cheating but it makes them irrelevant. C has "Sequential Consistency" under this constraint too.