单例模式:确保某个类只有一个实例,并自行实例化向整个系统提供这个实例
实现单例模式的关键点:
通过将单例类的构造函数私有化,使得代码不能通过new的形式手动构造类对象,单例类会暴露一个公有静态方法来获取唯一对象
public class Singleton {
private static final Singleton instance = new Singleton();
// 构造函数私有化
private Singleton(){}
// 公有静态函数,对外暴露获取单例对象的接口
public static Singleton getInstance() {
return instance;
}
}
Singleton对象是静态对象,并且在声明的时候就已经初始化,保证了对象的唯一性
public class Singleton {
private static Singleton instance;
// 构造函数私有化
private Singleton(){}
// 公有静态函数,对外暴露获取单例对象的接口
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
getInstance方法添加了synchronized关键字,这就是所说的在多线程的情况下保证单例对象唯一性的手段。
优点:只有在使用时才会实例化,一定程度上节约了资源
缺点:第一次加载时需要及时实例化,反应稍慢,每次调用都进行同步,造成不必要的同步开销
DCL方式的优点是既能够在需要时才初始化实例,又能够保证线程安全,且单例对象初始化后调用getInstance不进行同步锁
public class Singleton {
private volatile static Singleton instance = null;
// 构造函数私有化
private Singleton(){}
// 公有静态函数,对外暴露获取单例对象的接口
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
DCL模式是使用最多的单例实现方式。优点:资源利用率高,效率高。缺点:第一次加载时反应稍慢,也由于Java内存模型的原因偶尔会失败,在高并发情况下也有一定缺陷。
DCL失效问题: 假设线程A执行到instance = new Singleton()语句,这里看起来是一句代码,但实际上他并不是一个原子操作,这句代码最终会编译成多条汇编指令,大致如下:
1. 给Singleton的实例分配内存
2. 调用Singleton()的构造函数,初始化成员字段
3. 将instance对象指向分配的内存空间(此时instance就不是null了)
由于Java编译器允许处理器乱序执行,在JDK1.5之前Java内存模型中Cache,寄存器到主内存回写顺序的规定,上面2,3的顺序无法保证就可能出现132的错误顺序。
DCL虽然一定程度上解决了资源消耗、多余的同步、线程安全等问题,但在某些情况下还是会失效。在《Java并发编程实践》一书中建议使用如下代码替代:
public class Singleton {
// 构造函数私有化
private Singleton(){}
// 公有静态函数,对外暴露获取单例对象的接口
public static Singleton getInstance() {
return SingletonHolder.instance;
}
private static class SingletonHolder {
private static final Singleton instance = new Singleton();
}
}
枚举单例最大的优点就是写法简单,并且在任何情况下它都是一个单例。(以上的方式在反序列化的情况下会出现重新创建对象)
public class Singleton {
// 构造函数私有化
private Singleton(){}
// 公有静态函数,对外暴露获取单例对象的接口
public static Singleton getInstance() {
return SingletonEnum.INSTANCE.getInstance();
}
private enum SingletonEnum {
INSTANCE;
private Singleton singleton;
// JVM会保证此方法只调用一次
SingletonEnum() {
singleton = new Singleton();
}
public Singleton getInstance() {
return singleton;
}
}
}
以上其他模式要杜绝单例对象在被反序列化时重新生成对象,需要加入如下方法:
private Object readResolve() throws ObjectStreamException {
return instance;
}
《Effective Java》一书中的话:单元素的枚举类型已经成为实现Singleton的最佳方法。
public class SingletonManager {
private static Map objMap = new HashMap<>();
private SingletonManager() {}
public static void registerService(String key, Object instance) {
if (!objMap.containsKey(key)) {
objMap.put(key, instance);
}
}
public static Object getService(String key) {
return objMap.get(key);
}
}
在程序的初始,将多种单例类型注入到一个统一的管理类中,使用时根据key获取对象对应类型的对象。
可参考Android中ContextImpl类中的SYSTEM_SERVICE_MAP
不管以哪种形式实现单例,核心原理都是将构造函数私有化,并通过静态方法获取唯一的实例,在获取过程中必须保证线程安全、防止反序列化等问题,选择哪种实现方式取决于项目本身。
优点:
由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁地创建、销毁时,而且创建或销毁时性能又无法优化,单例模式的优势就非常明显了
由于单例模式只生成一个实例,所以,减少了系统的性能开销,当一个对象的产生需要比较多的资源时,如读取配置,产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后用永久驻留内存的方式来解决
单例模式可以避免对资源的多重占用,例如一个写文件操作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作
单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如,可以设计一个单例类,负责所有数据表的映射处理
缺点:
单例模式一般没有接口,扩展很困难,若要扩展,除了修改代码基本上没有第二种途径可以实现
单例对象如果持有Context,那么很容易引发内存泄漏,此时需要注意传递给单例对象的Context最好是Application Context