Avoid Double-Checked Locking for Lazy Initialization
What is it?
Double-checked locking is a pattern used to reduce synchronization overhead when initializing an object. It involves checking an object's state both before and after a synchronized
block to determine initialization necessity.
Why apply it?
Double-checked locking, except for int
and float
, does not reliably ensure thread safety in a platform-independent way. It risks a thread using an uninitialized instance while another is still creating it. Modern JVMs optimize method synchronization, making this pattern unnecessary and potentially harmful.
How to fix it?
Use a synchronized method to ensure thread safety during initialization. Alternatively, employ a static inner class, which guarantees lazy and safe initialization by the JVM.
Examples
Example 1:
Negative
In the negative example, double-checked locking is used, which is not thread-safe.
public class ResourceFactory {
private static Resource resource;
public static Resource getInstance() {
if (resource == null) { // Noncompliant
synchronized (ResourceFactory.class) {
if (resource == null) {
resource = new Resource();
}
}
}
return resource;
}
private static class Resource { /* Resource class implementation */ }
}
Example 2:
Positive
In the positive example, thread safety is ensured by synchronizing the entire method.
public class ResourceFactory {
private static Resource resource;
public static synchronized Resource getInstance() { // Compliant
if (resource == null) {
resource = new Resource();
}
return resource;
}
private static class Resource { /* Resource class implementation */ }
}
Negative
In the negative example, the double-checked locking pattern results in potential race conditions.
public class ResourceFactory {
private static Resource resource;
public static Resource getInstance() {
if (resource == null) { // Noncompliant
synchronized (ResourceFactory.class) {
if (resource == null) {
resource = new Resource();
}
}
}
return resource;
}
private static class Resource { /* Resource class implementation */ }
}
Example 3:
Positive
In the positive example, a static inner class is used for lazy initialization, ensuring thread safety.
public class ResourceFactory {
private static class ResourceHolder {
public static final Resource resource = new Resource(); // Compliant
}
public static Resource getResource() {
return ResourceHolder.resource;
}
private static class Resource { /* Resource class implementation */ }
}