简体   繁体   中英

Convert double check locking from using synchronized to locks in JAVA

Consider the following code implementing double check locking using the synchronized keyword in JAVA 8:

private static void redoHeavyInitialisation() {
    if (needToReinitialise()) {
        synchronized (MyClass.class) {
            if (needToReinitialise()) {
                doHeavyInitialisation();
            }
        }
    }
}

The reason double check locking is used is because the initialisation is heavy (hence lazy) AND it can happen more than once (hence singleton pattern can not be used, correct me if I am wrong).

Anyway, first, how do you convert the code above to use Lock from the JAVA concurrent package instead of using synchronized keyword?

Only after that AND optionally, feel free to comment on using Lock or synchronized keyword which one is better.

Remember, this question is not about Lock vs synchronized comparison. Answer attempts without answering the code conversion part will not be picked as accepted answer.

Transformation of synchronized blocks to the equivalent block using ReentrantLock is pretty rote.

First you create a lock with the same or similar scope and lifetime as the object you were locking on. Here you are locking on MyClass.class , hence a static lock, so you can map this to a static lock in MyClass , such as MyClass.initLock .

Then just replace each:

synchronized (object) {

with

lock.lock();
try {

and each associated closing brace with

} finally {
  lock.unlock();
}

Putting it all together you have:

private final static ReentrantLock initLock = new ReentrantLock();

private static void redoHeavyInitialisation() {
    if (needToReinitialise()) {
        MyClass.initLock.lock();
        try {
            if (needToReinitialise()) {
                doHeavyInitialisation();
            }
        } finally {
          MyClass.initLock.unlock();
        }
    }
}

Performance-wise there is little daylight between the approaches. They essentially have the same semantics and usually use similar underlying mechanisms. In the past, there have been performance differences - sometimes optimizations have gone in that affect one or the other, so on some JVMs you can find a difference, but the whole point of double checked locking is to avoid taking the lock anyway, so just do what's simplest. You only get the lock for a very small transitory period while the needToReinitialise() method is running, so the locking cost won't have any ongoing impact.

Consider the following code:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HeavyInitializer {
static final Logger logger = LoggerFactory.getLogger(HeavyInitializer.class);
static HeavyInitializer singleton;
public static synchronized HeavyInitializer getInstance() {
    if (singleton==null) {
        singleton = new HeavyInitializer();
    }
    return singleton;
}
boolean initialized;
private HeavyInitializer() {
    initialized = false;
}

public synchronized void initialize() {
     if (!initialized) {
         heavyStuffDoneHere();
     }
}
public synchronized void reInitilize() {
    if (needToReinitialise()) {
        heavyStuffDoneHere();
    }
}

private void heavyStuffDoneHere() {
    initialized = true;
}

private boolean needToReinitialise() {
    if (!initialized)
       return false;
    boolean ret = false;
    //Do your check here... and set ret     
    return ret;
}

}

From Oracle's doc :

... then making these methods synchronized has two effects:

  • First, it is not possible for two invocations of synchronized methods on the same object to interleave. When one thread is executing a synchronized method for an object, all other threads that invoke synchronized methods for the same object block (suspend execution) until the first thread is done with the object.

  • Second, when a synchronized method exits, it automatically establishes a happens-before relationship with any subsequent invocation of a synchronized method for the same object. This guarantees that changes to the state of the object are visible to all threads.

Trying to use Lock would be trying to reimplement the synchronized block. Not necessary.

Singleton Double checks the lock and prevents singleton object to break using serialization.

package pattern.core.java; import java.io.Serializable;

public class Singleton extends Object implements Serializable {

private static final long serialVersionUID = 1L;
private static Singleton sg;

private Singleton() {
}

public static Singleton getSingletonObj() {
    if (sg == null) {
        synchronized (sg) {
            if (sg == null) {
                sg = new Singleton();
            }
        }
    } 
    return sg;
}


/*
 * this method ensures that new object will not be created for singleton
 * class using serialization and deserialization
 */
protected Object readResolve() {
    return sg;
}

/*
 * @Override protected Object clone() throws CloneNotSupportedException {
 * throw new CloneNotSupportedException(); }
 */

@Override
protected Object clone() throws CloneNotSupportedException {
    return sg;
}

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM