75

here is my custom class for singleton pattern. in this code, I use double-checked locking as below. As I read many posts on some source, they say that double check is useful because it prevents two concurrent threads run at same times make two different objects.

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;
        }
}

I still don't understand above code so much. What is the problem, if two threads together run same line of code when instance is null ?

if (searchBox == null) {
                synchronized (SearchBox.class) {
                    if (searchBox == null) {
                        searchBox = new SearchBox();
                    }
                }
            }

When that appear. both two threads will see object is null. then both synchronize. and then, they check again, and still see it null. and create two different objects. OOOPS.

Please explain for me. What have I understand wrong ?

Thanks :)

4

4 回答 4

88

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

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

于 2013-08-07T02:46:04.983 回答
32

让我们看一下这段代码:

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

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

顺便说一句,在 Java 中,实现单例的最佳方式是使用单元素enum类型。从有效的Java

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

于 2013-08-07T02:48:57.600 回答
12

仅当您担心许多线程同时调用单例或通常需要获取锁的成本时,才需要这种双重检查锁。

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

查看此链接了解更多信息。

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

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

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

于 2013-08-07T11:58:34.577 回答