Picture two threads, A and B, calling getInstance() at the same moment. Both see an empty field. Both build an object. Now you have two singletons, which is exactly the bug the pattern was meant to stop.
The classic fix is double-checked locking, and the lesson walks through it step by step. The method checks the field once without a lock. If it is empty, the thread takes a lock and checks again inside it. Thread A gets the lock, sees the field is still empty, and builds the object. Thread B waits on the lock. When B gets in, its second check finds the object, so it skips building and returns it.
The first check exists for speed. After the object exists, no thread ever touches the lock again, and each call is a plain field read. The second check exists for correctness.
Each language has its own gotcha. In Java the field must be declared volatile, or a thread can see a half-built object. Bill Pugh's write-up on the Java memory model explains why, and notes that volatile fixes it from Java 5 on. A Java enum with one value avoids the problem, because the JVM builds enum constants itself. Go has sync.Once, which runs a function exactly once across all goroutines. In Node.js, a module is cached after the first require, so exporting one instance from a module already acts as a singleton.