Yes they have accounted for synchronization by using java.util.concurrent.atomic.AtomicLong, which is a synchronized version of Long, to store the seed, but they didn’t explicitly make Math.random and Random “synchronized”.
Does anyone know why Math.random() uses double-checked locking? I read that dcl can cause very unexpected behaviours (like assigning a reference to an object before the constructor has run)
Now you’re just being obtuse. Syncronised means more than just using the ‘syncronised’ keyword.
Regardless, there’s thread-contention structures within it that make inadvisable to call repeatedly from multiple threads. The double-checked-locking is suspicious as well. Which is why it’d be a much better idea to just use a Random object per-thread.
DCL is OK on newer VMs as long as the variable is declared volatile. There is some discussion though, if volatile DCL has any benefits over straight forward synchronized lazy instantiation.
Now you’re just being obtuse. Syncronised means more than just using the ‘syncronised’ keyword.
Not quite. The atomic primitive operations will simply to be promoted to a single CPU opcode. Where a synchronized block is (minimally) something like: zero counter, set reg to lock-mutex value, atomic swap, did I get the lock, if no do stuff, if yes do the block, set reg to unlock-mutex value and atomic swap. So the real cost of Atomic ops is that if the data is loaded into the cache of some other core when a second one modifies it, then the memory controller needs to invalidate the cached info in the first and reload (after whatever write-back scheme is done from the operation triggered by the second.) Of course the same applies to accessing the mutex memory location for the synchronized block…ugly.
My suggestion is to avoid the problem. Don’t use a global number generator (or very rarely). Then you don’t need to worry about contention.
What is double-checked locking?

What is double-checked locking?
Short story: Using lazy initialization to init a field by checking if the field is null twice (first one is outside a synchronized block, the second is inside a synchronized block) to make sure the field only gets initialized once. Kinda like this:
// Broken multithreaded version
// "Double-Checked Locking" idiom
class Foo {
private Helper helper = null;
public Helper getHelper() {
if (helper == null) {
synchronized(this) {
if (helper == null) {
helper = new Helper();
}
}
}
return helper;
}
// other functions and members...
}
It may seem to be correct code, but that code can go very wrong since the compiler can re-arrange instructions.
Making the helper field volatile will solve this problem though.
What could go wrong with that? And the Random object inside Math isn’t volatile
In the math case it does really matter. If more then one happens to be allocated, all but one will end up being collected.
What can go wrong is that since helper isn’t volatile, then compiler may remove the second compare to null, since by static analysis it’s already be “shown” to be null in the first.
Also the newer VMs memory model ensure that volatile variables are always accessed via their real memory location (or something like that) to prevent multithreaded code to work on the CPU-cached value, which could be different in multicore systems.