关于“双重检查锁定”的另一个问题

时间:2019-03-08 06:17:02

标签: java concurrency singleton

我已经根据“有效的Java”#83修改了“正常的” 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();
    }
}

3 个答案:

答案 0 :(得分:1)

差异是这一行:

my = instance;

您正在将两个对象都引用到JVM堆上的一个位置。之后,您要致电:

my = new MySystem();

,这会使myinstance都不为空(您无法链接=运算符,因此仅实例化my)。然后调用此:

MySystem.getInstance().getDate()

您不是在null上调用method。

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

答案 1 :(得分:1)

发生以下情况时,您将获得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;
}

答案 2 :(得分:0)

好的,让我逐步解释一下。 线程A:my == null。 线程B:my == null,然后进行同步,然后是“ instance = my = new MySystem()”,并返回my,它不是null。 线程A:获取同步,然后输入“ instance!= null”,并返回my,它为null。

NPE,砰!因此,必须在第二次检查之前使用“我的实例”。

如何解释“有效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;
}