简体   繁体   English

没有 volatile 的双重检查锁是错误的?

[英]double check lock without volatile is wrong?

i use jdk1.8.我使用 jdk1.8。 i think that double check lock without volatile is right.我认为没有 volatile 的双重检查锁是正确的。 I use countdownlatch test many times and the object is singleton.我多次使用 countdownlatch 测试,对象是单例。 How to prove that it must need “volatile”?如何证明它一定需要“volatile”?

update 1更新 1

Sorry, my code is not formatted, because I can't receive some JavaScript public class DCLTest {抱歉,我的代码没有格式化,因为我无法接收一些 JavaScript 公共类 DCLTest {

private static /*volatile*/ Singleton instance = null;

static class Singleton {

    public String name;

    public Singleton(String name) {
        try {
            //We can delete this sentence, just to simulate various situations
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.name = name;
    }
}

public static Singleton getInstance() {
    if (null == instance) {
        synchronized (Singleton.class) {
            if (null == instance) {
                instance = new Singleton(Thread.currentThread().getName());
            }
        }
    }
    return instance;
}

public static void test() throws InterruptedException {
    int count = 1;
    while (true){
        int size = 5000;
        final String[] strs = new String[size];
        final CountDownLatch countDownLatch = new CountDownLatch(1);
        for (int i = 0; i < size; i++) {
            final int index = i;
            new Thread(()->{
                try {
                    countDownLatch.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Singleton instance = getInstance();
                strs[index] = instance.name;
            }).start();
        }
        Thread.sleep(100);
        countDownLatch.countDown();
        Thread.sleep(1000);
        for (int i = 0; i < size-1; i++) {
            if(!(strs[i].equals(strs[i+1]))){
                System.out.println("i = " + strs[i] + ",i+1 = "+strs[i+1]);
                System.out.println("need volatile");
                return;
            }
        }
        System.out.println(count++ + " times");
    }
}

public static void main(String[] args) throws InterruptedException {
    test();
}

} }

The key problem that you are not seeing is that instructions can be reordered.您没有看到的关键问题是指令可以重新排序。 So the order they are in the source code, isn't the same as they are applied on memory.因此,它们在源代码中的顺序与它们在内存中的应用顺序不同。 CPU's and compilers are the cause or this reordering. CPU 和编译器是原因或这种重新排序。

I'm not going through the whole example of example of double checked locking because many examples are available, but will provide you just enough information to do some more research.我不会详细介绍双重检查锁定示例的整个示例,因为有很多示例可用,但将为您提供足够的信息来进行更多研究。

if you would have the following code:如果您有以下代码:

 if(singleton == null){
     synchronized{
         if(singleton == null){
            singleton = new Singleton("foobar")
         }
     }
 }

Then under the hood something like this will happen.然后在引擎盖下会发生这样的事情。

if(singleton == null){
     synchronized{
         if(singleton == null){
            tmp = alloc(Singleton.class)
            tmp.value = "foobar"
            singleton = tmp
         }
     }
 }

Till so far, all is good.到目前为止,一切都很好。 But the following reordering is legal:但以下重新排序是合法的:

if(singleton == null){
     synchronized{
         if(singleton == null){
            tmp = alloc(Singleton.class)
            singleton = tmp
            tmp.value = "foobar"
         }
     }
 }

So this means that a singleton that hasn't been completely constructed (the value has not yet been set) has been written to the singleton global variable.所以这意味着尚未完全构造的单例(尚未设置值)已写入单例全局变量。 If a different thread would read this variable, it could see a partially created object.如果一个不同的线程读取这个变量,它可以看到一个部分创建的对象。

There are other potential problems like atomicity (eg if the value field would be a long, it could be fragmented eg torn read/write).还有其他潜在的问题,如原子性(例如,如果值字段很长,它可能会被碎片化,例如读/写撕裂)。 And also visibility;还有可见性; eg the compiler could optimize the code so that the load/store from memory is optimized-out.例如,编译器可以优化代码,以便优化内存中的加载/存储。 Keep in mind that thinking in term of reading from memory instead of cache, is fundamentally flawed and the most frequently encountered misunderstandings I see on SO;请记住,从内存而不是缓存中读取的思考从根本上是有缺陷的,也是我在 SO 上看到的最常遇到的误解; even many seniors get this wrong.甚至很多前辈都弄错了。 Atomicity, visibility and reordering are part of the Java memory model, and making the singleton' variable volatile, resolves all these problems.原子性、可见性和重新排序是 Java 内存模型的一部分,并且使单例变量可变,解决了所有这些问题。 It removes the data race (you can look it up for more details).它消除了数据竞争(您可以查找它以获取更多详细信息)。

If you want to be really hardcore, it would be sufficient to place a [storestore] barrier between the creation of an object and the assignment to the singleton and a [loadload] barrier on the reading side.如果你想成为真正的核心,在创建对象和分配给单例之间放置一个 [storestore] 屏障就足够了,在读取端放置一个 [loadload] 屏障就足够了。 But this goes well beyond what most engineers understand and it won't make much of a performance difference in most situations.但这远远超出了大多数工程师的理解,并且在大多数情况下不会对性能产生太大影响。

If you want to check if something can break, please check out JCStress:如果您想检查某些东西是否会损坏,请查看 JCStress:

https://github.com/openjdk/jcstress https://github.com/openjdk/jcstress

It is a great tool and can help you help you to show that your code is broken.它是一个很棒的工具,可以帮助您证明您的代码已损坏。

How to prove that it must need “volatile”?如何证明它一定需要“volatile”?

As a general rule, you cannot prove correctness of a multi-threaded application by testing.作为一般规则,您无法通过测试来证明多线程应用程序的正确性。 You may be able to prove incorrectness, but even that is not guaranteed.也许能够证明不正确,但即使如此也不能保证。 As you are observing.正如你在观察。

The fact that you haven't succeeded in making your application fail is not a proof that it is correct.您没有成功地使您的应用程序失败的事实并不能证明它是正确的。

The way to prove correctness is to do a formal (ie mathematical) happens before analysis.证明正确性的方法是分析之前进行形式化(即数学)。

It is fairly straightforward to show that when the singleton is not volatile there are executions in which there is a missing happens before .可以很直接地表明,当singleton不是volatile ,在执行之前会发生缺失。 This may lead to an incorrect outcome such as the initialization happening more than once.可能会导致不正确的结果,例如多次进行初始化。 But it is not guaranteed that you will get an incorrect outcome.但不能保证您会得到不正确的结果。

The flip-side is that if a volatile is used, the happens before relationships combined with the logic of the code are sufficient to construct a formal (mathematical) proof that you will always get a correct outcome.另一方面是,如果使用volatile ,则发生在关系与代码逻辑相结合之前发生的事情足以构建正式(数学)证明,您将始终得到正确的结果。


(I am not going to construct the proofs here. It is too much effort.) (我不打算在这里构造证明。太费力了。)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 为什么java双重检查锁定单例必须使用volatile关键字? - why java double check lock singleton must use the volatile keyword? 没有 volatile 的双重检查锁定(但使用 VarHandle 释放/获取) - double check locking without volatile (but with VarHandle release/acquire) 这是Double Check Locking的更好版本,没有易失性和同步开销 - Is this a better version of Double Check Locking without volatile and synchronization overhead 双重检查锁定没有挥发性 - Double-checked locking without volatile 如何打破双重检查锁定没有volatile - How to break double checked locking without volatile 在Java单例双空检查实现中使用volatile - Use of volatile in singleton double null check implementation in java 双重检查锁定而不使用volatile-keyword并且不同步整个getInstance()方法 - Double checked locking without using volatile-keyword and without synchronizing the entire getInstance() method 使用双重检查锁定实现单例时,我们是否需要 volatile - do we need volatile when implementing singleton using double-check locking 为什么在执行双重检查锁定时将volatile字段复制到局部变量 - Why is the volatile field copied to a local variable when doing double check locking 同步无波动 - synchronize without volatile
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM