先展示一段 double check lock思想的单例模式代码:
public class SingletonLh4 {
private static volatile SingletonLh4 singleton;
private String instanceName;
private SingletonLh4() {
this.instanceName = "SingletonLh4";
}
public static SingletonLh4 getInstance() {
if (singleton == null) {
synchronized (SingletonLh4.class) {
if (singleton == null) {
singleton = new SingletonLh4();
}
}
}
return singleton;
}
}
如果在不考虑 volatile 关键字的情况下,如下图所示:
public class SingletonLh4 {
private static SingletonLh4 singleton; // 1
private String instanceName;
private SingletonLh4() {
this.instanceName = "SingletonLh4";
}
public static SingletonLh4 getInstance() {
if (singleton == null) {
synchronized (SingletonLh4.class) {
if (singleton == null) {
singleton = new SingletonLh4();
}
}
}
return singleton;
}
}
如上图,按照代码逻辑分析后,大多数人确定这样应该也能100%保证单例实例的创建,感觉没啥问题。但是如果大家能考虑到cpu执行指令的时候允许乱序的,那么就需要重新分析上图代码了。
如果上述代码 1 处 SingletonLh4 singleton 没有volatile关键字的修饰,多个线程同时调用 getInstance() 的时候,只有一个线程能获取锁,并给 singleton 创建一个 SingletonLh4 实例,但是这个 new SingletonLh4() 是一个多指令操作,实例化的过程包括: instanceName 的实例化和 singleton 的实例化。根据 jvm 运行时指令重排序和 Happens-Before 规则,singleton 实例化和instanceName 实例化并没有先后顺序的约束,所以有可能出现 instanceName 先实例化,然后再实例化 singleton 。这个时候恰好有另一个线程调用 getInstance() 方法,发现 singleton 不为空,这个时候就直接返回 singleton 实例,其实这个实例不是一个完整的实例,因为 instanceName 是为空的。
如果使用了volatile关键字的修饰,那么 new SingletonLh4() 中的 singleton 实例化和instanceName 实例化会受到 Happens-Before 原则的约束,实例化时不会发生乱序,所以会先实例化 instanceName,再实例化 singleton 。所以不会出现返回的 singleton 实例不完整的现象。