单例模式
定义
确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
使用场景
确保某个类有且只有一个对象的场景,避免产生多个对象消耗过多的资源,或者某种类型的对象应该有且只有一个。Android常通过context.getSystemService()获取,系统核心服务以单例模式存在,减少了资源消耗
实现
实现单例的注意事项:
1. 构造函数不对外开放(private)
2. 通过一个静态方法或者枚举返回单例对象
3. 确保任何时候单例类的对象有且只有一个(eg:多线程)
4. 确保单例类对象在反序列化时不会重新构建对象
使用时,通过静态方法获取到单例类的唯一对象
饿汉模式
利用静态成员会在类初始化时自行创建来保证只有一个实例
public class Singleton{
private static final Singleton instance=new Singleton();
//构造函数私有
private Singleton(){
}
public static Singleton getInstance(){
return instance;
}
}
由于instance是静态变量,在类初始化时就会创建并存留在堆中(Java7 之前,常量池的存储在方法区;Java7 及 Java8,将常量池的存储转移到了 堆。但不管怎样,使用new操作符产生的对象实例存储在堆中) 这样确保了单例类实例只会创建一次。(初始化顺序依次是(静态变量、静态初始化块)>(变量、初始化块)>构造器)
懒汉模式
在首次调用获取实例的方法时创建对象
public class Singleton{
private static Singleton instance;
private Singleton(){
}
public static synchronized Singleton getInstance(){
if(instance == null){
instance=new Singleton();
}
return instance;
}
}
优点:
- 只在使用时才会实例化,节约资源
缺点:
- 第一次加载时及时进行实例化反应稍慢
- 每次调用getInstance()都进行同步,造成不必要的同步开销
双重锁模式(DCL)
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;
}
}
优点:
- 只在使用时才会实例化,节约资源
- 相比懒汉式单例,避免了不必要的同步
缺点:
- 第一次加载时及时进行实例化反应稍慢
静态内部类实现
public class Singleton{
private Singleton(){
}
public static Singleton getInstance(){
return SingletonHolder.instance;
}
//静态内部类
public static class SingletonHolder{
private static final Singleton instance = new Singleton();
}
}
利用静态内部类只在静态内部类首次调用才初始化的性质
枚举单例
public enum SingletonEnum{
INSTANCE;
}
优点
- 代码简单
- 默认枚举实例的创建在Java中是线程安全的
容器实现单例模式
public class SinlgetonManager{
private static Map<String,Object> objMap = new HashMap<String,Object>();
private SinlgetonManager(){
}
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获取对象对应类型的对象
总结
优点
- 减少了内存开支,特别是某个对象需要频繁创建时
- 减少系统性能开销,特别是创建对象需要比较长时间时
- 避免了对一些资源的多重占用
- 设置全局的访问点,可以优化和共享资源访问
缺点
- 一般单例模式没有接口,扩展很困难
- 内存泄漏: 单例对象如果持有Context,很容易引发内存泄漏,因此尽量使用生命周期最长的ApplicationContext