Here's how the double-checked locking pattern works:
The volatile keyword ensures that changes to the instance variable are immediately visible to all threads, preventing potential visibility issues.
In the getInstance() method, the first check is performed outside of synchronization. This is an optimization to avoid synchronization overhead if the instance is already created.
If the instance is still null after the first check, a synchronized block is entered using the class itself (ThreadSafeSingleton.class) as the lock.
Inside the synchronized block, a second check is performed to ensure that another thread hasn't created the instance while the current thread was waiting to acquire the lock.
If the instance is still null after the second check, a new instance of the singleton class is created.
Subsequent calls to getInstance() return the already created instance without entering the synchronized block, improving performance.
It's important to note that while the double-checked locking pattern can improve performance compared to locking the entire getInstance() method, it requires careful implementation to ensure correctness. In modern Java versions (Java 5 and later), you can also consider using the java.util.concurrent.atomic.AtomicReference class or other thread-safe mechanisms to achieve singleton patterns without the intricacies of double-checked locking.