1.单机版单例
日常开发中,为了性能考虑,可能要延迟实例化一些对象,这时可能的代码如下
public class SingletonDemo {
private SingletonDemo() {
}
private static SingletonDemo instance;
public static SingletonDemo getInstance() {
if (instance == null) {
instance = new SingletonDemo();
}
return instance;
}
}
代码很简单,看着貌似也没问题,动手测下效果。
public static void main(String[] args) {
List<Thread> list = new ArrayList<>();
for (int i = 0; i <10 ; i++) {
list.add(new Thread(() -> {
String name = Thread.currentThread().getName();
System.out.println(name+SingletonDemo.getInstance());
}));
}
for (int i = 0; i <list.size() ; i++) {
list.get(i).start();
}
}
运行结果可知,多线程运行时,实例了多个对象,没有单例效果。
2. 双重检查锁版单例
然后调整下SingletonDemo 的代码,在运行一遍
public class SingletonDemo {
private SingletonDemo() {
}
// 1-1
private static volatile SingletonDemo instance;
public static SingletonDemo getInstance() {
// 第一层检查1-2
if (instance == null) {
synchronized (SingletonDemo.class) {
// 1-3 第二层检查
if (instance == null) {
// 1-4
instance = new SingletonDemo();
}
}
}
return instance;
}
}
注意: 在1-1处,添加了volatile ,这是用于防止指令重排的。
实例化SingletonDemo对象分为三个步骤
- 分配内存空间
- 初始化对象
- 将初始化好的对象分配给内存地址
如果没有volatile,执行顺序可能为 123或者132;
假如当A线程进行初始化,执行132,这时B线程来访问,此时instance 分配了地址,执行到1-2处,此时返回的就是一个不完整的对象了。