单例模式的持续改进
Posted WXCOP无限靠谱
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了单例模式的持续改进相关的知识,希望对你有一定的参考价值。
引言
单例模式是我们在日常开发中使用比较多的一种设计模式,它主要是保证系统中有且仅有一个对象实例存在,这样做的好处是:不必每次都创建一个对象实例,减少浪费。
传统单例模式的写法与问题
早在阎宏的《Java与模式》一书中提到了饿汉式与懒汉式单例模式的写法。
饿汉式单例模式写法
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance() {
return instance;
}
}
懒汉式单例模式写法
public class Singleton {
private static Singleton instance;
private Singleton() {}
public synchronized static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
存在问题
饿汉式单例存在的问题是在类加载时就会初始化创建对象,而没有等到真正使用对象时才创建,存在一些小小的浪费。 懒汉式单例虽然实现了延迟加载,但存在的问题主要在并发上。虽然加了synchronized关键字,但这个锁是在整个对象实例上的,所以效率牺牲比较大。
饿汉式单例模式的优化
如果要避免在初始化类的时候就创建对象,可以把对象封装在内部类中,随方法创建而创建。
public class BetterSingleton {
private BetterSingleton() {}
public static BetterSingleton getInstance() {
return SingletonHolder.instance;
}
private static class SingletonHolder {
private static BetterSingleton instance = new BetterSingleton();
}
}
懒汉式单例模式的优化
优化并发的一个原则就是减少锁的持有时间,所以很常见的一个写法就是“双重加锁机制”:
public class DoubleLockSingleton {
private static DoubleLockSingleton instance;
private DoubleLockSingleton() {
}
public static DoubleLockSingleton getInstance() {
if (instance == null) {
synchronized (DoubleLockSingleton.class) {
if (instance == null) {
instance = new DoubleLockSingleton();
}
}
}
return instance;
}
}
但这里还有一个小小的问题,就是并发中的重排序问题,现代CPU为了加速指令的执行,常常通过重排序技术达到效果。比如这里的new DoubleLockSingleton()操作就不是一个原子操作,实际上会分解成下面3步:
给instance分配内存,在栈中分配并初始化为null;
调用Singleton的构造函数,生成对象实例,在堆中分配;
把instance指向在堆中分配的对象。
由于指令重排序优化,执行顺序可能会变成1,3,2,那么当一个线程执行完1,3之后,被另一个线程抢占,这时instance已经不是null了,就会直接返回。然而2还没有执行过,也就是说这个对象实例还没有初始化过。 解决方法就是在实例变量前加上volatile修饰符,防止对象重排序。更新后的代码如下:
public class DoubleLockSingleton {
private volatile static DoubleLockSingleton instance;
private DoubleLockSingleton() {
}
public static DoubleLockSingleton getInstance() {
if (instance == null) {
synchronized (DoubleLockSingleton.class) {
if (instance == null) {
instance = new DoubleLockSingleton();
}
}
}
return instance;
}
}``
注意:volatile关键字在JDK1.5版本后才支持。
使用Java中的enum创建单例
说了这么多,其实在《Efective Java》这本书中,Bloch还介绍了一种很好的创建单例的方法—就是利用enum特性:enum会天然保证创建出来的对象是单例且是同步的。
public enum EnumSingleton {
INSTANCE;
public static EnumSingleton getInstance() {
return INSTANCE;
}
}
应该说,这是最优雅的一种方式了吧! :)
观点碰撞1
石墨嘻:最后一种是最优雅的一种,但却有个致命的弱点:具有不可测试性,因为 instance 无法被替换。
丁一:enum单例,利用强大的powermock,还是能测的。下面是一个例子:
// 被测代码
以上是关于单例模式的持续改进的主要内容,如果未能解决你的问题,请参考以下文章