单例模式应用场景:确保某个类有且只有一个对象,避免产生多个对象消耗过多的资源。例如访问I/O、数据库资源等;
单例模式关键点:
1.构造函数私有化
2.通过静态方法或者枚举返回单例对象
3.确保单例类有且只有一个
4.确保单例类在反序列化时不会重新构建对象
实现方式:
1.饿汉式
public class Singleton { private Singleton(){} private static final Singleton instance = new Singleton(); public static Singleton getInstance(){ return instance; } }
2.懒汉式
public class Singleton { private Singleton(){} private static Singleton instance ; public static synchronized Singleton getInstance(){ if(instance == null){ instance = new Singleton(); } return instance; } }
懒汉式优点是instance在使用时才会被实例化,缺点是第一次实例化时反应稍慢,每次调用getInstance()时都要进行同步,造成不必要的同步开销;
3.Double check:
public class Singleton { private Singleton(){} private static Singleton instance; public static Singleton getInstance(){ if(instance == null){ synchronized (Singleton.class){ if(instance == null) { instance = new Singleton(); } } } return instance; } }
优点:在需要时才进行初始化单例,保证线程安全,且单例初始化后调用getInstance时不进行同步锁
缺点:在某些情况下造成双重检查锁定失效问题
4.静态内部类模式
public class Singleton { private Singleton(){} public static Singleton getInstance(){ return SingletonHolder.instance; } private static class SingletonHolder { private static final Singleton instance = new Singleton(); } }
类级内部类相当于其外部类的static成员,它的对象与外部类对象间不存在依赖关系,相互独立,因此可直接创建。而对象级内部类的实例,是必须绑定在外部对象实例上的。
类级内部类只有在第一次被使用的时候才被会装载。
5.枚举单例:
上面几种情况会在反序列化时重新创建对象;而枚举和普通类一样,不仅能够有字段,也有方法,默认枚举实例的创建是线程安全的,并且在任何情况下都是一个单例;
public enum SingletonEnum {
INSTANCE;
public void doSomething() {
System.out.println("doSth");
}
}
使用如下:
public class TestMain {
@Test
public void main() throws Exception{
SerializeSingletonEnum();//序列化
SingletonEnum s = DeserializeSingletonEnum();//反序列
}
private SingletonEnum DeserializeSingletonEnum() throws Exception, IOException {
// TODO Auto-generated method stub
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
new File("E:/Person.txt")));
SingletonEnum s = (SingletonEnum) ois.readObject();
System.out.println(s.hashCode());
ois.close();
return s;
}
private void SerializeSingletonEnum() throws FileNotFoundException,IOException {
// TODO Auto-generated method stub
ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream(
new File("E:/Person.txt")));
SingletonEnum s = SingletonEnum.INSTANCE;
oo.writeObject(s);
System.out.println(s.hashCode());
oo.close();
}
}
实验结果:
392292416
392292416
两个对象的hashcode是一样的,说明enum单例在反序列化时,不重新创建对象;
如果其他单例模式要想在反序列化时也不重新创建对象,可以增加readResolve方法,返回instance;如下:
protected Object readResolve() throws ObjectStreamException {
return instance;
}
5.使用容器实现单例模式
public class SingletonManager {
private static Map<String,Object> objMap = new HashMap<String,Object>();
private SingletonManager() {}
public static void registerService(String key,Object object) {
if(!objMap.containsKey(key))
objMap.put(key, object);
}
public static Object getService(String key) {
return objMap.get(key);
}
}
总结:不管哪种形式的单例模式,核心是构造方法私有化,通过静态方法获取一个唯一的实例,获取过程中保证线程安全,反序列化时不重新创建对象;
选择哪种模式取决于项目本身,如是否复杂的并发环境,JDK版本是否过低,单例对象的资源消耗等;
android源码中的单例模式
在android系统中,经常通过Context获取系统服务,如WindowManagerService、ActivityManagerService,更常用的是LayoutInflater,这些服务会在合适的时候以单例的形式注册在系统中,在我们需要的时候通过Context.getSystemService(String name)获取。
以LayoutInflater为例:
LayoutInflater.from(context).inflate(layoutId,null);
public static LayoutInflater from(Context context) { LayoutInflater LayoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); if (LayoutInflater == null) { throw new AssertionError("LayoutInflater not found."); } return LayoutInflater; }
context的实现类是ContextImpl,g跟踪源码可以发现虚拟机在加载该类时会注册各种ServiceFetcher,其中包括LayoutInflater Service.将这些服务以键值对的形势存储 在一个HashMap中,用户使用时 只需要跟据key来获取到相关服务。当第一次获取时,会调用ServiceFetcher的CreateService方法创建服务对象,然后缓存到一个列表中,之后直接从缓存中取,避免重复获取对象。此种方式就如使用容器实现单例模式一样;