Java的双重检查锁定(Java double checked locking)

2019-09-01 06:16发布

我偶然发现了一篇文章最近在Java和它的陷阱讨论双重检查锁定模式,现在我想知道如果我已经使用多年,模式的变体,现在受到的任何问题。

我已经看了很多帖子和文章的主题,并了解可能存在的问题与得到的部分构造的对象的引用,而据我所知,我不认为我的实现是受这些问题。 是否有与下面的模式的任何问题?

而且,如果没有,为什么没有人使用它? 我从来没有见过它在任何我解决这个问题,看到讨论的建议。

public class Test {
    private static Test instance;
    private static boolean initialized = false;

    public static Test getInstance() {
        if (!initialized) {
            synchronized (Test.class) {
                if (!initialized) {
                    instance = new Test();
                    initialized = true;
                }
            }
        }
        return instance;
    }
}

Answer 1:

双重检查锁坏了 。 由于初始化是一种原始的,它可能不需要它是挥发性的工作,但是没有什么能够阻止被初始化的初始化实例之前被视为真到了非syncronized代码。

编辑:为了澄清上述答案,原来的问题询问使用布尔控制双重检查锁定。 如果没有上面的链接的解决方案,它不会工作。 你可以仔细检查锁在实际设置一个布尔值,但你仍然对指令重新排序问题,当谈到创建类的实例。 建议的解决方案不起作用,因为你看到初始化布尔在非syncronized块为真后,例如可能不被初始化。

仔细检查锁定适当的解决方案是使用挥发性(在实例字段),忘了初始化布尔值,并确保使用JDK 1.5或更大是,或在最终字段初始化它,如在链接阐述文章和汤姆的回答,或者根本就没有使用它。

当然,整个概念似乎是一个巨大的不成熟的优化,除非你知道你会得到一吨线程争对得到这个单身,或者你已经成型的应用,并看到这是一个热点。



Answer 2:

如果这样做是可以initializedvolatile 。 正如synchronized的有趣的效果volatile是不是真的那么做以参考因为我们可以说对其他数据。 建立的instance字段和Test对象是被迫发生,之前initialized 。 当通过短路使用高速缓存的值,则initialize之前发生的读instance和对象通过参考到达。 有在具有一个单独的无显著差异initialized标志(除了它会导致在代码甚至更复杂)。

(对于规则final在构造为不安全出版物字段是有点不同。)

然而,你应该很少看到这种情况下的错误。 使用时首次陷入麻烦的可能性是最小的,这是一个不重复的比赛。

该代码是过于复杂。 你可以只是把它写为:

private static final Test instance = new Test();

public static Test getInstance() {
    return instance;
}


Answer 3:

双重检查锁定确实打破,解决问题的方法其实更容易实现代码方面比这个成语 - 只需使用一个静态初始化。

public class Test {
    private static final Test instance = createInstance();

    private static Test createInstance() {
        // construction logic goes here...
        return new Test();
    }

    public static Test getInstance() {
        return instance;
    }
}

静态初始化保证了JVM加载类的第一次执行,类引用之前可以返回到任何线程 - 使它固有线程。



Answer 4:

这就是为什么双检锁是坏的原因。

同步担保,只有一个线程可以输入一个代码块。 不过,这并不保证,即同步段内完成变量的修改将是其他线程可见。 只进入上述同步块的螺纹保证看到的变化。 这就是为什么双检锁是坏的原因 - 它不是在读者的侧同步。 读线程可以看到,该单不为空,但单数据可能无法完全初始化(可见)。

排序是通过提供volatilevolatile保证订货,例如写入该写入单对象写入非易失性静态字段之前将完成挥发性单静态字段的保证。 这并不妨碍创建两个对象的单身人士,这是通过同步提供。

级决赛静态字段不需要挥发。 在Java中, JVM需要这个问题的关心。

请参阅我的文章, 回答Singleton模式和打破双重检查锁定在真实世界中的Java应用程序 ,说明相对于双重检查锁定,看起来聪明,但坏了一个单身的例子。



Answer 5:

你或许应该在使用原子数据类型java.util.concurrent.atomic中 。



Answer 6:

如果“初始化”是真实的,那么“实例”必须充分初始化,同1加1等于2 :)。 因此,该代码是正确的。 实例只实例化一次,但该功能可以被称为万次,因此不会提高而不检查同步一百万减一倍的性能提升。



Answer 7:

我一直在研究有关双重检查锁定成语,从我的理解,你的代码可能会导致的,除非你的测试类是不可变的阅读部分构造实例的问题:

Java存储模型提供初始化安全的共享不可变对象一个特殊的保证。

他们可以安全地访问,即使同步不用于发布对象引用。

(从最明智的书Java并发实践语录)

因此,在这种情况下,双重检查锁定成语会工作。

但是,如果情况并非如此,观察你不同步返回可变实例,因此,实例变量可能不能完全构建(你会看到的属性,而不是在构造函数中提供的值的默认值)。

布尔变量不添加任何东西,以避免该问题,因为它可能会被设置为true,测试类初始化之前(synchronized关键字不能完全避免重新排序,一些sencences可能更改顺序)。 有没有之前发生规则的Java内存模型保证。

并且使布尔挥发性不会添加任何东西,因为32个变量在Java中原子产生。 双重检查锁定成语会与他们合作也是如此。

由于Java 5,就可以解决这个问题声明实例变量的波动。

你可以阅读更多关于双重检查成语这个非常有趣的文章 。

最后,一些建议我读:

  • 试想,如果你应该使用Singleton模式。 它被认为是许多人的反模式。 依赖注射是优选的,其中可能的。 检查这个 。

  • 仔细考虑,如果双重检查锁定优化是实现它之前,确有必要的,因为在大多数情况下,这不会是值得的。 另外,还要考虑在静态字段构建测试类,因为构造一类需要大量资源的时候,在大多数的时代,这是不是这样的延迟加载是唯一有用的。

如果您仍然需要执行这个优化,检查此链接 ,其提供了实现类似的效果,你想什么一些替代品。



Answer 8:

DCL的问题是破碎的,尽管它似乎在许多虚拟机的作品。 有一个关于此问题的一个很好的书面记录http://www.javaworld.com/article/2075306/java-concurrency/can-double-checked-locking-be-fixed-.html 。

多线程和内存一致性比他们可能会出现更复杂的主题。 [...]可以忽略所有这些复杂的,如果你只使用Java提供出于这样的目的的工具 - 同步。 如果同步每次访问可能已被写入,或者可以通过读取的变量,另一个线程,你不会有任何内存一致性的问题。

妥善解决这个问题的唯一办法是避免synchronized块内初始化工作(做翘首)或单检查。 使用布尔的initialized相当于在基准本身就是一个空检查。 第二个线程可能会看到initialized是真实的,但instance可能仍然为空或部分初始化。



Answer 9:

双重检查锁定是反模式。

延迟初始化holder类是你应该看的格局。

尽管有这么多其他的答案,我想我应该回答,因为仍然没有一个简单的答案,说为什么DCL在许多情况下被打破,为什么它是不必要的,你应该做的,而不是。 因此,我将使用从报价Goetz: Java Concurrency In Practice这对我提供了对Java内存模型的最后一章最succint解释。

这是关于变量安全公告:

与DCL真正的问题是在假设没有时同步读出的共享对象的引用是错误地看到的过时的值(在这种情况下,空)可能发生的最坏的事情; 在这种情况下,DCL成语用持有的锁再次试图弥补这种风险。 但最坏的情况实际上是相当糟糕的,它是可以看到的参考,但对于对象的状态过时的值的电流值,这意味着该对象可以被看作是一个无效的或不正确的状态。

在JMM(Java 5.0和更高版本)的后续变化使DCL如果资源是由挥发性的工作,并在此对性能的影响很小,因为挥发性读取通常只比非易失性读取更加昂贵。

然而,这是一个成语,其效用已经在很大程度上传入的,它促其(慢竞争的同步,慢JVM启动)力不再在游戏中,使得作为优化其效果较差。 懒惰初始化持有者成语提供同样的好处,是比较容易理解。

清单16.6。 延迟初始化holder类成语。

 public class ResourceFactory private static class ResourceHolder { public static Resource resource = new Resource(); } public static Resource getResource() { return ResourceHolder.resource; } } 

这是做到这一点的方式。



Answer 10:

首先,对于单身您可以使用一个枚举,因为在这个问题解释的实施辛格尔顿有一个枚举(在Java中)

其次,由于Java 1.5中,你可以使用双检查锁定volatile变量,因为在这篇文章的末尾解释说: https://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html



Answer 11:

还有一些情况下,当可以使用双重检查。

  1. 首先,如果你真的不需要一个单,并仔细检查用于只是不创建和初始化到许多对象。
  2. 有一个final设定在构造/初始化块(使所有先前初始化字段被其他线程可以看到)的结束字段。


文章来源: Java double checked locking