确保某一个类的只有一个实例,而且自行实例化并向整个系统提供这个实例。使用场景:
• 可以避免产生多个对象消耗过多的资源,如I/O访问等。
• 某些类的对象就是应该只有一个,多个对象将导致逻辑错误或混乱。
下面是单例模式常见的两种实现方式 饿汉模式 和 双重锁模式
• 饿汉模式
public class Singleton {
private static Singleton mInstance = new Singleton();
private Singleton() { }
public static Singleton getInstance() {
return mInstance;
}
}
饿汉式是最简单的实现方式,这种实现方式适合那些在初始化时就要用到单例的情况。简单粗暴,不管你用不用得着这个实例,先给你创建(new)出来。如果单例对象初始化非常快,而且占用内存非常小的时候这种方式是比较合适的,可以直接在应用启动时加载并初始化。但是,如果单例初始化的操作耗时比较长而应用对于启动速度又有要求,或者单例的占用内存比较大,再或者单例只是在某个特定场景的情况下才会被使用,而一般情况下是不会使用时,使用饿汉式的单例模式就是不合适的,这时候就需要用到懒汉式的方式去按需延迟加载单例。
• 双重锁模式(DCL 实现)
public class Singleton {
private static Singleton mInstance = null;
private Singleton() { }
public static Singleton getInstance() {
if (mInstance == null) {
synchronized (Singleton.class) {
if (mInstance == null) {
mInstance = new Singleton();
}
}
}
return mInstance;
}
}
还有一种就是懒汉模式,就是在用的时候才在getInstance() 方法中完成实例的创建(new),同时给这个方法添加synchronized 关键字,可以确保在多线程情况下单例依旧唯一,但是懒汉模式每次调用getInstance 方法时由于synchronized 的存在,需要进行同步,造成不必要的资源开销。因此便有了双重锁模式的实现方式。这样既避免了饿汉模式的缺点,又解决了懒汉模式的不足;确保单例只在第一次真正需要的时候创建。
DCL这种模式的亮点在于getInstance()方法上,其中对singleton 进行了两次判断是否空。
• 第一层判断是为了避免不必要的同步
• 第二层的判断是为了在null的情况下才创建实例。
具体我们来分析一下:
假设线程A执行到了singleton = new Singleton(); 语句,这里看起来是一句代码,但是它并不是一个原子操作,这句代码最终会被编译成多条汇编指令,它大致会做三件事情:
但是由于Java编译器允许处理器乱序执行,以及在jdk1.5之前,JMM(Java Memory Model:java内存模型)中Cache、寄存器、到主内存的回写顺序规定,上面的步骤b 步骤c的执行顺序是不保证了。也就是说执行顺序可能是1-2-3,也可能是1-3-2,如果是后者的指向顺序,并且恰恰在3执行完毕,2尚未执行时,被切换到线程B中,这时候因为singleton在线程A中执行了步骤3了,已经非空了,所以,线程B直接就取走了singleton,再使用时就会出错。这就是DCL失效问题。
但是在JDK1.5之后,官方给出了volatile关键字,将singleton定义的代码改成:
private volatile static Singleton singleton; //使用volatile 关键字
这样就解决了DCL失效的问题。volatile的作用是:
更多
在某些复杂的场景中,上述的两种方式都或多或少的存在一些缺陷。因此便有了以下两种单例模式的实现方式。
• 静态内部类
public class Singleton {
private Singleton(){ }
public static Singleton getInstance(){
return SingletonHolder.sInstance;
}
/**
* 静态内部类
*/
private static SingletonHolder{
private static final Singleton sInstance = new Singleton();
}
}
当第一次加载 Singleton 类时,并不会初始化 sInstance,只有在第一次调用 Singleton 的 getInstance 方法时才会导致 sInstance 被初始化。因此,第一次调用 getInstance 方法会导致虚拟机加载 SingletonHolder 类,这种方式不仅能够确保线程安全,也能够保证单例对象的唯一性,同时也延迟了单例的实例化,所以这是推荐使用的单例模式的实现方式。
• 枚举单例
publicenum Singleton {
//定义一个枚举的元素,它就是Singleton的一个实例
INSTANCE;
public void doSomething(){
//do something
}
}
定义一个枚举元素,而他就是单例;可以说,这是实现单例最简单最实惠的方式;可以有效的避免单例在反序列化的过程中被创建,从而让单例变得不唯一。默认枚举实例的创建是线程安全的(创建枚举类的单例在JVM层面也是能保证线程安全的), 所以不需要担心线程安全的问题,所以理论上枚举类来实现单例模式是最简单的方式。
不可否认枚举会使得代码更易读更安全,我们在很多经典的Java书已经看到推荐使用枚举来代替int常量了,但是Android官网不建议使用枚举,因为占用内存多。特别是大型的App中,能不用则不用。因为它会牺牲执行的速度和并大幅增加文件体积。这也是性能优化中减少OOM的一个方面。
单例模式是运用频率很高的模式,但是,由于在客户端通常没有高并发的情况,因此,选择哪种实现方式并不会有太大影响。即便如此,出于效率考虑,推荐用DCL、静态内部类的形式。
缺点: