0

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;
}
errantlinguist
  • 3,658
  • 4
  • 18
  • 41
  • 1
    The monitor used for you synchronized block is not constant - that's doesn't look right. What is the problem with synchronizing on `this` (or a `private final Object lock - new Object();`)? – assylias Aug 03 '17 at 11:13
  • 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`. However, I never thought about using a lock/mutex object; That could be an answer I'd accept. – errantlinguist Aug 03 '17 at 11:17
  • I would like to highlight that synchronizing on the entire object `this` does not influence the "methods which don't affect the state of `field`", i.e. calling these methods will block only if they contain the `synchronized(this)`; see https://stackoverflow.com/questions/11981227/synchronizedthis-blocks-whole-object – Radu Dumbrăveanu Aug 03 '17 at 11:47

2 Answers2

4

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) { ... }
assylias
  • 321,522
  • 82
  • 660
  • 783
0

You definitely do not want to lock on a null mutable member. Most people lock on this or the class. But if you do not want to do that, the solution by assylias works well for your use case. You can also look through the java.util.concurrent.locks package. It provides ways of doing locking manually without using the synchronized block. Maybe a ReentrantLock would work for you? The package also has some cool things like condition variables and spin locks (probably not what you would use here though). Also, I wrote an article on the singleton pattern and the use of double-checked locking and the "holder" pattern that might be interesting to you. https://medium.com/@michael.andrews/deconstructing-the-singleton-b5f881f85f5

Michael Andrews
  • 828
  • 8
  • 13