Search code examples
javasynchronizationmemberweak-referencesdouble-checked-locking

Double-checked locking of non-null class member field


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)?

Example

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;
}

Solution

  • 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) { ... }