Search code examples
javamultithreadingjakarta-eevolatile

Is this a better version of Double Check Locking without volatile and synchronization overhead


Below code snippet is from Effective Java 2nd Edition Double Checked Locking

// Double-check idiom for lazy initialization of instance fields

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

From what i know the main Problem with Double Checked Locking is the reordering inside second check locking so that the other thread might see the values of field/result as set which may be infact still be in executing. To avoid this we make the reference of field as volatile to gurantee visibility and reordering.

But this can be achieved by the below code also

private FieldType field; // non volatile
private volatile boolean fence = false;

FieldType getField() {
    if (field == null) {  // First check (no locking) // no volatile read
        synchronized(this) {   //  inside synch block no problem of visibilty will latest           //value  of field 
            if (field == null) {// Second check (with locking)  
                Object obj =  computeFieldValue();
             fence = true; // any volatile write will take. this will make sure statements are //not reorder with setting field as non null.
            field = (FieldType)obj; // this will be only set after computeFieldValue has been //completed fully
           }
        }
    }
    return field;
}

So after when the initialization have been done, then no thread will have to for volatile read or synchronization overhead. Please see if my assumptions are right or not?


Solution

  • There is no way to achieve "cheap" double-checked locking in pure JMM; something got to give.

    Your solution doesn't work because a volatile write can be reordered with a following normal action. See jsr133 cookbook for allowed reordering in the so called "roach motel" model. "Roach motel" is a stronger model than JMM, so if your solution fails in roach motel, it fails in JMM.

    roach motel model
    reordering between a normal action and a volatile/monitor action
    
       --                              <--
      |                                   |
      |    VolatileLoad / MonitorEnter    | forbidden
      |                                   |
       --> allowed                      --
    
    
       --> allowed                      --
      |                                   | 
      |    VolatileStore / MonitorExit    | forbidden
      |                                   |
       --                              <--
    

    There is a way to deter reordering of two normal actions in "roach motel" model

    (1) action#1
    (2) volatile write
    (3) volatile read
    (4) action#4
    

    (1) cannot be reordered with (2), and (4) cannot be reordered with (3), therefore (1) and (4) cannot be reordered.

    However, be warned that "roach motel" model is a stronger model than JMM. You cannot be sure that a JVM conforms to roach motel model. For a concrete example

    action#1
    synchronized(new Object()){}
    synchronized(new Object()){}
    action#4
    

    according to roach motel, action#1 and action#4 can't be reordered; however JVM can legitimately (allowed by JMM) remove the two sync block then reorder the remaining two actions.