【问题标题】:Convert double check locking from using synchronized to locks in JAVA将双重检查锁定从使用同步转换为 JAVA 中的锁定
【发布时间】:2017-02-01 01:18:34
【问题描述】:

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

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

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

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

仅在此之后 AND 可选,随意评论使用 Lock 或 synchronized 关键字哪个更好。

请记住,这个问题与 Lock 与同步比较无关。没有回答代码转换部分的回答将不会被选为接受的答案。

【问题讨论】:

  • Synchronization vs Lock的可能重复
  • @ErwinBolwidt 是的,如果您只关注比较并且完全忽略这个要求等效代码转换的问题,这是主要问题。不想评论的可以不用评论,但请回答代码转换。
  • 你可以使用单例。一种可能的架构是使用同步方法创建一个 Singleton HeavyInitializer 类来初始化和重做初始化。
  • @Chocksmith 我不知道使用单例进行重新初始化的方法,因为我知道的所有单例模式都依赖于单例实例的非并发初始化。在创建第一个也是唯一一个实例后如何重新触发?
  • 你不应该和那些试图帮助你的人说话,好像他们是在替你做家庭作业的仆人一样。

标签: java multithreading concurrency java.util.concurrent double-checked-locking


【解决方案1】:

使用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();
        }
    }
}

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

【讨论】:

  • 谢谢。几乎是最佳答案。您能否评论一下为什么它必须是 ReentrantLock 而不是 Lock 的其他变体?
  • ReentrantLock 是我在 Java 8 中所知道的 Lock 接口的唯一可直接实例化的实现。您在谈论哪些其他实现? @user1589188
  • 对不起,我的意思是锁包。其他如读/写、标记、锁支持。
  • 不知道为什么要在这里使用它们。 ReentrantLocksynchronized 的直接替代品,适合您的使用。其他的完全是其他东西,在“锁支持”的情况下甚至没有锁。 @user1589188
  • ReentrantLock 是同步的直接替代品。有了这条附加信息,一切都很好。
【解决方案2】:

考虑以下代码:

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

...那么使这些方法同步有两个效果:

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

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

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

【讨论】:

  • 感谢您的努力。我很想给你打分,但你在那里使用synchronized 就像我的问题中的例子一样,它根本没有回答我的问题。
  • 只是试图贡献和展示一种替代架构。我的经验表明,使用锁或信号量重新实现同步将导致难以解决执行时间的错误。我会使用同步的单例架构。
【解决方案3】:

单例双重检查锁并防止单例对象使用序列化破坏。

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

}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-08
    • 1970-01-01
    • 2019-04-04
    • 1970-01-01
    相关资源
    最近更新 更多