关于 "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();
这使得 my
和 instance
都不为空(你不能链接 =
运算符,所以只有 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;
}
我根据"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();
这使得 my
和 instance
都不为空(你不能链接 =
运算符,所以只有 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;
}