将双重检查锁定从使用 synchronized 转换为锁定 JAVA

Convert double check locking from using synchronized to locks in JAVA

考虑以下代码在 JAVA 8 中使用 synchronized 关键字实现双重检查锁定:

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

使用双重检查锁定的原因是因为初始化很重(因此很懒惰)并且它可能发生不止一次(因此不能使用单例模式,如果我错了请纠正我)。

无论如何,首先,如何将上面的代码转换为使用 JAVA 并发包中的 Lock 而不是使用 synchronized 关键字?

只有在那之后,并且可以随意评论使用 Lock 或 synchronized 关键字哪个更好。

记住,这个问题不是关于 Lock 与 synchronized 的比较。未回答代码转换部分的回答尝试将不会被采纳为已接受的答案。

考虑以下代码:

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

}

来自Oracle's doc

...然后使这些方法同步有两个效果:

  • 首先,对同一个对象的同步方法的两次调用不可能交错。当一个线程正在为一个对象执行同步方法时,所有其他为同一对象调用同步方法的线程将阻塞(暂停执行),直到第一个线程完成该对象。

  • 其次,当同步方法退出时,它会自动与同一对象的任何后续同步方法调用建立 happens-before 关系。这保证了对对象状态的更改对所有线程都是可见的。

尝试使用 Lock 将尝试重新实现同步块。没必要。

使用 ReentrantLock 将同步块转换为等效块非常死记硬背。

首先,您创建一个与您锁定的对象具有相同或相似范围和生命周期的锁。这里你锁定在 MyClass.class,因此是静态锁,所以你可以将其映射到 MyClass 中的静态锁,例如 MyClass.initLock.

然后只需替换每个:

synchronized (object) {

lock.lock();
try {

和每个关联的右大括号

} finally {
  lock.unlock();
}

综合起来你有:

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 引道之间几乎没有日光。它们本质上具有相同的语义并且通常使用相似的底层机制。过去,存在性能差异 - 有时优化会影响其中一个或另一个,因此在某些 JVM 上您可以找到差异,但是双重检查锁定的 整点 无论如何都是为了避免拿锁,所以只做最简单的事情。当 needToReinitialise() 方法是 运行 时,您只会在非常短的时间内获得锁定,因此锁定成本不会产生任何持续影响。

Singleton Double 检查锁并防止单例对象使用序列化中断。

包pattern.core.java; 导入 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;
}

}