我已经根据“有效的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();
}
}
答案 0 :(得分:1)
差异是这一行:
my = instance;
您正在将两个对象都引用到JVM堆上的一个位置。之后,您要致电:
my = new MySystem();
,这会使my
和instance
都不为空(您无法链接=
运算符,因此仅实例化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;
}