不带volatile的双重检查锁是否错误

lg40wkob  于 2021-08-20  发布在  Java
关注(0)|答案(2)|浏览(396)

我使用jdk1.8。我认为没有volatile的双重检查锁是正确的。我多次使用Countdownlock测试,对象是singleton。如何证明它一定需要“易失性”
更新1
抱歉,我的代码没有格式化,因为我无法接收一些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();
}

}

zpjtge22

zpjtge221#

如何证明它一定需要“易失性”
一般来说,您不能通过测试来证明多线程应用程序的正确性。你也许能够证明自己的错误,但即使这样也不能保证。正如你所观察到的。
您没有成功地使应用程序失败的事实并不能证明它是正确的。
证明正确性的方法是在分析之前进行形式(即数学)分析。
singleton 不是 volatile 在执行死刑的过程中,有一个失踪的案件发生过。这可能会导致错误的结果,例如多次发生初始化。但不能保证你会得到错误的结果。
另一方面,如果 volatile 如果使用,则“先发生后发生”关系与代码逻辑相结合,足以构造一个形式(数学)证明,证明您将始终获得正确的结果。
(我不打算在这里构造证明。这太费劲了。)

omhiaaxx

omhiaaxx2#

您没有看到的关键问题是指令可以重新排序。因此,它们在源代码中的顺序与应用于内存的顺序不同。cpu和编译器是导致这种重新排序的原因。
我不会完整介绍双重检查锁定的示例,因为有很多示例可用,但将为您提供足够的信息,以便进行更多的研究。
如果您有以下代码:

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

然后在引擎盖下会发生类似的事情。

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

到目前为止,一切都很好。但以下重新排序是合法的:

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

这意味着一个尚未完全构造的单例(值尚未设置)已写入单例全局变量。如果另一个线程读取此变量,它可能会看到部分创建的对象。
还有其他潜在问题,如原子性(例如,如果值字段较长,则可能会出现碎片,例如读/写中断)。以及能见度;e、 g.编译器可以优化代码,从而优化内存中的加载/存储。请记住,从内存而不是缓存中读取数据的思维方式是有根本缺陷的,我所看到的最常见的误解也是如此;甚至许多老年人也会误解这一点。原子性、可见性和重新排序是java内存模型的一部分,使单例变量不稳定可以解决所有这些问题。它消除了数据竞争(您可以查找更多详细信息)。
如果你想成为真正的硬核,在创建一个对象和分配给singleton之间设置一个[storestore]屏障和在读取端设置一个[loadload]屏障就足够了。但这远远超出了大多数工程师的理解,在大多数情况下,这不会对性能产生太大影响。
如果您想检查某个部件是否会断裂,请查看以下内容:
https://github.com/openjdk/jcstress
这是一个很好的工具,可以帮助您显示代码已损坏。

相关问题