Java Singleton中的双重检查锁定

Java Singleton中的双重检查锁定,java,multithreading,design-patterns,singleton,Java,Multithreading,Design Patterns,Singleton,这是我为singleton模式定制的类。在这段代码中,我使用双重检查锁定,如下所示。当我在一些源代码上读到很多文章时,他们说双重检查很有用,因为它可以防止两个并发线程同时运行,从而生成两个不同的对象 public class DoubleCheckLocking { public static class SearchBox { private static volatile SearchBox searchBox; // private constr

这是我为singleton模式定制的类。在这段代码中,我使用双重检查锁定,如下所示。当我在一些源代码上读到很多文章时,他们说双重检查很有用,因为它可以防止两个并发线程同时运行,从而生成两个不同的对象

public class DoubleCheckLocking {

    public static class SearchBox {
        private static volatile SearchBox searchBox;

        // private constructor
        private SearchBox() {}

        // static method to get instance
        public static SearchBox getInstance() {
            if (searchBox == null) { // first time lock
                synchronized (SearchBox.class) {
                    if (searchBox == null) {  // second time lock
                        searchBox = new SearchBox();
                    }
                }
            }
            return searchBox;
        }
}
我还是不太懂上面的代码。当实例为null时,如果两个线程一起运行同一行代码,问题是什么

if (searchBox == null) {
                synchronized (SearchBox.class) {
                    if (searchBox == null) {
                        searchBox = new SearchBox();
                    }
                }
            }
当它出现时。两个线程都将看到对象为null。然后两者同步。然后,他们再次检查,仍然看到它为空。并创建两个不同的对象。哎呀

请给我解释一下。我理解错了什么


谢谢:)

否,因为您正在获取
搜索框上的锁。class
,一次只有一个线程将进入同步块。因此,第一个线程进入,然后发现
searchBox
为null并创建它,然后离开同步块,然后第二个线程进入块,然后发现
searchBox
不是null,因为第一个线程已经创建了它,所以它不会创建
searchBox
的新实例


双重检查模式用于避免每次执行代码时获得锁。如果调用没有同时发生,那么第一个条件将失败,代码执行将不会执行锁定,从而节省资源。

只有当您担心多个线程同时调用singleton,或者担心获得锁的成本时,才需要此双重检查锁

它的目的是防止不必要的同步,从而使代码在多线程环境中保持快速


如果您在Java 1.5或更高版本中运行,并且在双重检查锁定机制中使用
volatile
关键字,那么它将正常工作。由于您使用的是
volatile
关键字,您的示例并没有根据上面的相同链接中断。

让我们看看以下代码:

1 if (searchBox == null) {
2     synchronized (SearchBox.class) {
3     if (searchBox == null) {
4         searchBox = new SearchBox();
5     }
6 }
让我们试着解释一下。假设我们有两个线程
A
B
,假设其中至少有一个到达第3行并观察到
searchBox==null
true
。由于
synchronized
块,两个线程不能同时位于第3行。这是理解双重检查锁定为何有效的关键。因此,必须是
A
B
先通过
synchronized
。在不丧失一般性的情况下,假设该线程是
A
。然后,当看到
searchBox==null
为true时,它将进入语句体,并将
searchBox
设置为
searchBox
的新实例。然后,它将最终退出
同步
块。现在轮到
B
进入:记住,
B
被阻止,等待
A
退出。现在,当它进入块时,它将观察
搜索框
。但是
A
将刚刚将
searchBox
设置为非
null
值就离开了。完成了

顺便说一下,在Java中,实现单例的最佳方法是使用单个元素
enum
type。发件人:

虽然这种方法还没有被广泛采用,但单元素枚举类型是实现单例的最佳方式

  • 如果已经创建了实例,则不要执行任何操作-避免锁定线程
  • 获取锁的第一个线程检查并发现没有这样的对象,然后创建它。它释放了锁,第二个也可以这样做——它必须检查对象是否存在,因为第一个对象可能已经创建了它

  • 因此,基本上外部
    if
    用于防止冗余锁定-它让所有线程都知道已经存在一个对象,它们不需要锁定/执行任何操作。内部
    if
    用于让并发线程知道另一个线程是否已经创建了该对象。

    对此我不太清楚,但显然这种双重检查锁被破坏了。这不符合你的期望@williammerrison谢谢。。。我以前没见过。。。现在来看一下,实际上,这个例子并没有被打破。在JDK1.5+内存模型中,将引用设置为volatile(如OP的代码中所述)“修复”了双重检查的锁定模式。啊,@jtahlborn因此非常清楚,如果所检查的变量是volatile的,并且我们在1.5或更高版本中运行,那么这种双重锁定模式不会被破坏吗?我个人对此很好奇。同样,我对这个“问题”知之甚少。如果方法声明为与一个空检查同步,那么缺点是什么?事实上,OP的版本确实有效(根据您链接的文章)。它不起作用。假设线程C转到第1行,当线程A执行第4行时,线程C可能会看到一个部分构造的对象。根据JMM,只有当两个线程锁定在同一个对象上时,它们才能保证看到相同的版本。与主存的同步可能发生在退出同步块之前。@jason根据“发生在”定义,在一个线程释放锁后,另一个线程获得锁,然后后者可以看到以前的更改。如果是这样,我认为不需要volatile关键字。你们能更详细地解释一下存在的含义吗?那个么我们为什么不在外部使用“synchronized”关键字呢?这也将一次停止多个线程访问同步块。请为C#world澄清完全相同的问题。我也是这么认为的吗
    if (searchBox == null) { //1
        synchronized (SearchBox.class) {
            if (searchBox == null) {  //2
                searchBox = new SearchBox();
                }
            }
        }
    }