Wiki Markup |
---|
The usual way of initializing an object is to use a constructor. Sometimes it is required to limit the number of instances of a member object to just one (this is similar to a singleton, however, the member object may or may not be {{static}}). In additionInstead of initializing using a constructor, sometimes a technique called lazy initialization is used to defer the construction of the member object until it is actually required. Reasons for incorporating lazy initialization include optimizing and breaking harmful circularities in class and instance initialization \[[Bloch 05|AA. Java References#Bloch 05]\]. |
For these purposes, instead of a constructor, a A class or an instance method is frequently used for lazy initialization, depending on whether the member object is static
or not. The method checks whether the instance has already been created and if not, creates it. If the instance already exists, it simply returns it. This is shown below:
Code Block |
---|
|
// Correct single threaded version using lazy initialization
final class Foo {
private Helper helper = null;
public Helper getHelper() {
if (helper == null) {
helper = new Helper();
}
return helper;
}
// ...
}
|
...
Code Block |
---|
|
// Correct multithreaded version using synchronization
final class Foo {
private Helper helper = null;
public synchronized Helper getHelper() {
if (helper == null) {
helper = new Helper();
}
return helper;
}
// ...
}
|
...
Code Block |
---|
|
// "Double-Checked Locking" idiom
final class Foo {
private Helper helper = null;
public Helper getHelper() {
if (helper == null) {
synchronized(this) {
if (helper == null) {
helper = new Helper();
}
}
}
return helper;
}
// otherOther functionsmethods and members...
}
|
Wiki Markup |
---|
According to the Java Memory Model (discussion reference) \[[Pugh 04|AA. Java References#Pugh 04]\]: |
...
This makes the originally proposed double-checked locking pattern insecure. The rule guideline CON26-J. Do not publish partially initialized objects further discusses further the possibility possible occurrence of a non-null reference to a helper
object that observes default values for of fields in the helper
object.
...
This compliant solution declares the Helper
object as volatile
and consequently, uses the correct form of the double-check checked locking idiom.
Code Block |
---|
|
// Works with acquire/release semantics for volatile
// Broken under JDK 1.4 and earlier
final class Foo {
private volatile Helper helper = null;
public Helper getHelper() {
if (helper == null) {
synchronized(this) {
if (helper == null) {
helper = new Helper(); // If the helper is null, create a new instance
}
}
}
return helper; // If helper is non-null, return its instance
}
}
|
...
Wiki Markup |
---|
This compliant solution initializes the {{helper}} in the declaration of the {{static}} variable \[[Manson 06|AA. Java References#Manson 06]\]. |
Code Block |
---|
|
final class Foo {
private static final Helper helper = new Helper(42);
public static Helper getHelper() {return helper;}
}
|
...
This compliant solution explicitly incorporates lazy initialization. It also uses a static
variable as suggested in the previous compliant solution. The variable is declared within a static
inner class, Holder
.
Code Block |
---|
|
final class Foo {
static Helper helper;
// Lazy initialization
private static class Holder {
static Helper helper = new Helper();
}
public static Helper getInstance() {
return Holder.helper;
}
}
|
...
Code Block |
---|
|
// Uses atomic utilities
final class Foo {
private final AtomicReference<Helper> helperRef =
new AtomicReference<Helper>();
public Helper getHelper() {
Helper helper = helperRef.get();
if (helper != null) {
return helper;
}
Helper newHelper = new Helper();
return helperRef.compareAndSet(null, newHelper) ?
newHelper :
helperRef.get();
}
}
|
Note that while this code ensures that only one Helper}]
object is preserved, it may potentially allow multiple {{Helper
objects to be created; with all but one being garbage-collected. But However, if constructing multiple Helper
objects is infeasible or expensive, then this solution might not may be appropriateinappropriate.
Compliant Solution (immutable
)
In this compliant solution the Foo
class is unchanged (it is immutable as before), but the Helper
class is made immutable. In this caseConsequently, the Helper
class is guaranteed to be fully constructed before becoming visible. The object must be truly immutable; it is not sufficient for the program to refrain from modifying the object.
Code Block |
---|
|
public final class Helper {
private final int n;
public Helper(int n) {
this.n = n;
}
// otherOther fields &and methods, all fields are final
}
final class Foo {
private Helper helper = null;
public Helper getHelper() {
if (helper == null) {
synchronized(this) {
if (helper == null) {
helper = new Helper(); // If the helper is null, create a new instance
}
}
}
return helper; // If helper is non-null, return its instance
}
}
|
Note that if class Foo
was were mutable, the Helper
field would need to be declared volatile
as shown in CON09-J. Ensure visibility of shared references to immutable objects. Also, the method getHelper()
is an instance method and the accessibility of the helper
field is private
. This allows safe publication of the Helper
object, in that, a thread cannot observe a partially initialized Foo
object (CON26-J. Do not publish partially initialized objects). The class Helper
is also compliant with CON26-J. Do not publish partially initialized objects and consequently, cannot be observed in a partially initialized state.
...