public class Singleton {
private Singleton() {}
private static Singleton single=null;
public static Singleton getInstance() {
if (single == null) {
synchronized (Singleton.class){
if (single == null) {
single = new Singleton();
}
}
}
return single;
}
}
上面示例大家看看是否是线程安全的呢,下面让我们详细分析下
1、先从创建对象讲起,对象的创建并不是一个原子操作,具体包含步骤如下
- 分配内存
- 初始化(调用构造函数来初始化成员变量,形成实例)
- 将对象指向分配的内存空间(执行完这步 对象是非 null了)
而由于可能重排序,无法保证第二、第三步顺序,即最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时对象已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错(因为没有初始化)
2、如何改进
- volatile关键字的一个作用是禁止指令重排
而由于可能重排序,无法保证第二、第三步顺序,即最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时对象已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错(因为没有初始化)
把"private static Singleton single=null;"调整为"private volatile static Singleton single=null;",声明为volatile之后,对它的写操作就会有一个内存屏障,这样,在它的赋值完成之前,就不用会调用读操作。



