单身双重锁定锁定

hqt*_*hqt 61 java singleton multithreading design-patterns

这是我的单例模式的自定义类.在这段代码中,我使用双重检查锁定如下.当我在某些源上阅读很多帖子时,他们说双重检查很有用,因为它可以防止两个并发线程同时运行产生两个不同的对象.

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;
        }
}
Run Code Online (Sandbox Code Playgroud)

我仍然不太了解上面的代码.如果两个线程在实例为空时一起运行相同的代码行,会出现什么问题?

if (searchBox == null) {
                synchronized (SearchBox.class) {
                    if (searchBox == null) {
                        searchBox = new SearchBox();
                    }
                }
            }
Run Code Online (Sandbox Code Playgroud)

当出现时.两个线程都会看到对象为null.然后两者同步.然后,他们再次检查,仍然看到它为空.并创建两个不同的对象.哎呀.

请解释一下.我有什么理解错了?

谢谢 :)

Aru*_*hny 67

不,因为您正在获取锁定,所以一次SearchBox.class只有一个线程将进入同步块.所以第一个线程进入然后查找searchBox并创建它然后离开synchronized块,然后第二个线程进入块然后它发现它searchBox不是null因为第一个线程已经创建了它所以它不会创建一个新的实例searchBox

双重检查模式用于避免每次执行代码时获取锁定,如果调用未一起发生,则第一个条件将失败,代码执行将不执行锁定,从而节省资源.

  • 实际上,_this_示例_not_ broken.在jdk 1.5+内存模型中,使引用volatile(如OP的代码中)"修复"双重检查锁定模式. (23认同)
  • 我对此并不了解,但显然这种双重检查锁被打破了.它根本无法实现[根据此](http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html) (5认同)
  • 如果声明方法只与一个空检查同步,那会有什么不利之处? (2认同)

jas*_*son 26

我们来看看这段代码:

1 if (searchBox == null) {
2     synchronized (SearchBox.class) {
3     if (searchBox == null) {
4         searchBox = new SearchBox();
5     }
6 }
Run Code Online (Sandbox Code Playgroud)

让我们试着解释一下.比方说,我们有两个线程A,并B让我们假设其中至少一个到达3号线和观察searchBox == nulltrue.由于块,两个线程不能同时在第3行synchronized.这是关键理解为什么双重检查锁定的工作方式.因此,它必须是首先AB通过它的情况synchronized.不失一般性,说那个线程是A.然后,当看到searchBox == null为真时,它将进入语句的主体,并设置searchBox为新的实例SearchBox.然后它最终将退出该synchronized区块.现在 B轮流进入:记住,B被阻止等待A退出.现在当它进入块时,它会观察到searchBox.但是,A只会设置searchBox为非null值.完成.

顺便说一句,在Java中,实现单例的最佳方法是使用单元素enum类型.来自Effective Java:

虽然这种方法尚未被广泛采用,但单元素枚举类型是实现单例的最佳方法.


Wil*_*son 11

只有当您担心同时调用单例的许多线程或者通常获得锁定的成本时,才需要此双重检查锁定.

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

有关更多信息,请查看此链接.

如果您使用的是Java 1.5或更高版本,并且volatile在双重检查锁定机制中使用该关键字,它将正常工作.当您使用volatile关键字时,您的示例不会根据上面的相同链接而中断.

  • Downvoter,原因?这是一个很好的答案. (4认同)

sta*_*an0 7

if (searchBox == null) { //1
    synchronized (SearchBox.class) {
        if (searchBox == null) {  //2
            searchBox = new SearchBox();
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)
  1. 如果已经创建了一个实例,不要做任何事情 - 避免锁定线程
  2. 获得锁的第一个线程检查并发现没有这样的对象并创建它。它释放锁,第二个可以做同样的事情——它必须检查对象是否存在,因为第一个可能已经创建了它。

所以基本上外部if用于防止冗余锁 - 它让所有线程知道已经有一个对象并且他们不需要锁定/做任何事情。内部if用于让并发线程知道另一个线程是否已经创建了该对象。