单例对象必须确保只有一个实例存在,一个类有且只有一个实例,而且自行实例化并向整个系统提供这个实例。
使用场景
确保某个类只有一个对象(实例)的场景,避免产生多个对象消耗过多的资源,例如某个对象创建需要消耗的资源过多,如要访问IO和数据库等资源。
实现
(1)、构造方法不对外开放,一般为private;
(2)、通过一个静态方法或者枚举返回单例类对象;
(3)、确保单例类的对象只有一个,尤其是在多线程环境下;
(4)、确保单例类对象在反序列化时不会重复构建对象。
示例(列举4种实现单例模式的方法)
(1)、饿汉单例模式
代码语言:javascript复制public class Singleton {
private final static Singleton INSTANCE = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return INSTANCE;
}
}
饿汉单例模式解决了多线程并发的问题,因为在加载这个类的时候,就实例化了instance
总结:饿汉单例模式的优点是写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题;缺点是在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
(2)、懒汉模式
懒汉模式是生命一个静态类对象,并且在用户第一次调用getInstance时进行初始化
代码语言:javascript复制public class Singleton{
private static Singleton INSTANCE;
private Singleton(){}
public static synchronized Singleton getInstance(){
if (null == INSTANCE){
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
懒汉模式使用了一个synchronized关键字,所以getInstance是一个同步方法,这就是上述所说的在多线程情况下保证单例对象唯一的一种手段。
总结:懒汉单例模式的优点是单例只在使用时才会被实例化,在一定程度上节约了资源;缺点是第一次加载时需要及时进行实例化,反应稍慢,最大的问题是每次调用getInstance都进行同步,造成不必要的同步开销,所以一般不建议使用
(3)、Double Check Lock(DCL)单例模式
代码语言:javascript复制public class Singleton {
private static Singleton INSTANCE = null;
private Singleton() {
}
public static Singleton getInstance() {
if (null == INSTANCE) {
synchronized (Singleton.class) {
if (null == INSTANCE) {
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
总结:INSTANCE = new Singleton();这条语句并不是一个原子操作,这句代码最终会被编译成多条汇编指令,大致做了以下三件事:
(i)、给Singleton的实例分配内存
(ii)、调用Singleton()的构造方法,初始化成员字段
(iii)、将INSTANCE对象指向分配的内存空间(此时INSTANCE就不是null)
DCL单例模式的优点是资源利用率高,第一次执行getInstance时单例才会被实例化,效率高;缺点是第一次加载时反应稍慢,也由于Java内存模型的原因偶尔会失败,在高并发环境下也有一定的缺陷。
(4)、静态内部类单例模式
代码语言:javascript复制public class Singleton {
private Singleton() {
}
public static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
总结:第一次加载Singleton类时并不会初始化INSTANCE,只有在第一次调用getInstance方法时才会导致INSTANCE被初始化。因此第一次调用INSTANCE调用getInstance方法会导致虚拟机加载SingletonInstance类,这种方式不仅能确保线程安全,也能保证单例对象的唯一性,同时也延迟了单例的实例化,所以这才是被推荐使用的单例模式创建方法。
还有一些其他的方式可以用来创建单例类,比如使用枚举实现单例模式,使用容器实现单例模式。
Android源码中的单例模式
在Android系统中,也有很多地方用到单例模式的,比如LayoutInflater类就是一个使用单例模式的类,还有很多的第三方库,也是用的单例模式,如EventBus等。
总结
单例模式是运用频率很高的模式,但是由于在客户端通常没有高并发的情况,因此选择这种实现方式不会有太大的影响,这里推荐使用上述说的静态内部类的方式去实现单例模式。
单例模式的优点:
(1)、由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁地创建和销毁时,而且创建或销毁时性能又无法优化,单例模式的优势就非常明显
(2)、由于单例模式只生产一个实例,所以减少了系统的性能开销,当一个对象的生产需要比较多的资源时,如读取配置、生产其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。
(3)、单例模式可以避免对资源的多重占用,例如一个写文件的操作,由于只有一个实例存在内存,避免对同一个资源文件的同时写操作。
(4)、单例模式可以在系统设置全局的访问点,优化和资源共享访问,例如,可以设计一个单例类,负责所有数据的映射关系。
单例模式的缺点:
(1)、单例模式一般没有接口,扩展难度比较高,若要扩展出了修改基本代码基本上没有第二种途径可以实现。
(2)、单例对象如果持有Context,那么很容易发生内存泄漏,此时需要注意传递给单例对象的Context最好是Application Context。
本文仅以个人学习总结为主