关于 "Double-Checked-Locking" 的另一个问题

One more question about the "Double-Checked-Locking"

我根据"effective java"#83修改了一个"normal"DCL单例,如下

import java.util.Date;

public class MySystem {
    private Date date = new Date();

    private MySystem() {};

    private static volatile MySystem instance;

    public Date getDate() {
        return date;
    }

    public static MySystem getInstance() {
        MySystem my = instance;
        if (my == null) {
            synchronized (MySystem.class) {
                if (instance == null) {
                    instance = my = new MySystem();
                }
            }
        }
        return my;
    }
}

但是当我运行它的时候,NullpointerException会被抛出的比例很高。当我如下修改时,一切正常。为什么?

import java.util.Date;

public class MySystem {
    private Date date = new Date();

    private MySystem() {};

    private static volatile MySystem instance;

    public Date getDate() {
        return date;
    }

    public static MySystem getInstance() {
        MySystem my = instance;
        if (my == null) {
            synchronized (MySystem.class) {
                my = instance;
                if (my == null) {
                    instance = my = new MySystem();
                }
            }
        }
        return my;
    }
}

主要内容如下。很难找出区别。

public class Main {
    public static void main(String[] args) {
        new Thread() {
            public void run() {
                System.out.println(MySystem.getInstance().getDate());
            }
        }.start();

        new Thread() {
            public void run() {
                System.out.println(MySystem.getInstance().getDate());
            }
        }.start();
    }
}

区别在于这一行:

my = instance;

您正在使两个对象引用都位于 JVM 堆上的一个位置。之后你打电话:

my = new MySystem();

这使得 myinstance 都不为空(你不能链接 = 运算符,所以只有 my 被实例化)。然后在调用这个之后:

MySystem.getInstance().getDate()

您没有在 null 上调用方法。

同步后,第二个线程正在等待 my 的实例化(为了调用这一行 my = instance)并且没有获得 NPE。

好吧,我一步一步来解释。 线程 A:我的 == null。 线程B:my == null,然后get sync,然后"instance = my = new MySystem()",还有return my,不为null。 线程A:get sync,然后"instance != null",return my,为null.

NPE,砰!所以第二次检查前的"my = instance"是必要的。

如何解释"Effective Java"中的例子?

// Double-check idiom for lazy initialization of instance fields
private volatile FieldType field;
private FieldType getField() {
    FieldType result = field;
    if (result == null) { // First check (no locking)
        synchronized(this) {
        if (field == null) // Second check (with locking)
            field = result = computeFieldValue();
        }
    }
    return result;
}

当发生以下情况时,您将获得 NPE:

public static MySystem getInstance() {
    MySystem my = instance;
    if (my == null) {                            // (1) instance was null => my is null and synchronized block is entered.
        synchronized (MySystem.class) {
            if (instance == null) {              // (2) instance was updated from another thread and is not null anymore.
                instance = my = new MySystem();
            }
        }
    }
    return my;
}

您会注意到,在这种情况下,引用 instance 不会复制到 my,它仍然是 null。您可以尝试以下方法来验证:

public static MySystem getInstance() {
    MySystem my = instance;
    if (my == null) {
        synchronized (MySystem.class) {
            if (instance == null) {
                instance = new MySystem();
            }
        }
        my = instance;
    }
    return my;
}