最近看到组里有人实现单例模式,采用静态内部类的方式,不是很懂这种写法的优点,查了一下各种写法的优缺点,总结一下。
内容多处参考文章:http://wuchong.me/blog/2014/08/28/how-to-correctly-write-singleton-pattern/
懒汉式
public class Single {
private static Single mInstance;
private Single(){}
// 线程不安全
public static Single getInstance() {
if (mInstance == null) {
mInstance = new Single();
}
return mInstance;
}
// 线程安全,效率低,只有一个线程能调用getInstance()方法。
public static synchronized Single getInstance() {
if (mInstance == null) {
mInstance = new Single();
}
return mInstance;
}
// 同步代码块加锁,双重检查锁。
public static Single getInstance() {
if (mInstance == null) { //Single Checked
synchronized (Single.class) {
if (mInstance == null) { //Double Checked
mInstance = new Single();
}
}
}
return mInstance ;
}
}
同步代码块加锁,双重检查
- 这个是平时最常用的方式,看似完美,其实是有问题的。
因为mInstance = new Single();
这句语句的执行,不是一个原子操作,JVM在执行这条语句时,做了3个操作。
- 给mInstance分配内存。
- 调用Single的构造方法进行初始化。
- 将mInstance对象指向分配的内存空间(执行完这步mInstance就非空啦)。
但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。
饿汉式
public class Single {
// 类加载时就被初始化,线程安全
private static final Single mInstance = new Single();
private Single(){}
public static Single getInstance() {
return mInstance;
}
}
缺点
- 不是
懒加载
模式,类被加载时就被初始化。 - 如果构造函数
需要传递参数
时,不能满足。
静态内部类
public class Single {
private Single(){}
private static class InnerHolder {
private static final INSTANCE = new Single();
}
public static Single getInstance() {
return InnerHolder.INSTANCE;
}
}
这种写法仍然使用JVM本身机制保证了线程安全问题;由于 InnerHolder 是私有的,除了 getInstance() 之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖 JDK 版本。
总结
单例模式
最好采用静态内部类
实现,但是如果对懒加载
和参数
没有要求,饿汉式
也可以。