I'm aware of the classic double-checked locking idiom for Java, which first checks if a given field is null
and, if so, acquires a lock on the class which has the field:
// Double-check idiom for lazy initialization of instance fields
// See Pascal Thivent's original answer <https://stackoverflow.com/a/3580658/1391325>
private volatile FieldType field;
FieldType getField() {
FieldType result = field;
if (result == null) { // First check (no locking)
synchronized(this) {
result = field;
if (result == null) { // Second check (with locking)
field = result = computeFieldValue();
}
}
}
return result;
}
However, in the case that field
will never be null (instead referencing "null object" value which is lazily replaced with a "non-null object"), can the synchronization on this
be refined to synchronized(field)
?
I've got a single, very large HugeObject
which can nevertheless be easily re-created. I want the garbage collector to be able to discard this instance in cases where memory is starting to run out, so I hold it with a Reference<HugeObject>
, and initialize it to Reference<HugeObject> field = new SoftReference<>(null)
. I'd prefer avoiding a lock on the entire object this
so that using methods which don't affect the state of field
can be called even during initialization of field
. Would it then be possible to simply acquire a lock on either this initial "null object" or the already-instantiated "non-null object", or could this result in subtle, unwanted concurrency effects? See code below:
private volatile Reference<HugeObject> field = new SoftReference<>(null);
HugeObject getField() {
HugeObject result = field.get();
if (result == null) {
synchronized(field) {
result = field.get();
if (result == null) {
result = computeFieldValue();
field = new SoftReference<>(result);
}
}
}
return result;
}
If you don't want to synchronize on this
, you can use another reference. But that reference needs to be constant. In your example, you are locking on field
which gets reassigned - so two threads could execute your method concurrently if they have different values for field
.
One standard solution is to use a dedicated lock:
private final Object lock = new Object();
//...
synchronized(lock) { ... }