1.单例模式的定义
确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
2.单例模式的使用场景
确保某个类有且只有一个对象的场景,避免产生多个对象消耗过多的资源,或者某种类型的对象应该有且只有一个。例如,创建一个对象需要消耗的资源过多,如果访问IO和数据库等资源,这时就要考虑使用单例模式。
3.实现单例模式关键点:
1)构造函数不对外开放,一般为Private;
2)通过一个静态方法或者枚举返回单例类对象;
3)确保单例类的对象有且只有一个,尤其是在多线程环境下;
4)确保单例类对象在反序列化时不会重新构建对象。
4.饿汉模式
饿汉模式比较占空间,典型的以空间换时间.
private class EHanShi{
//饿汉式,饿汉比较饿,刚开始就直接实例化了。
private static final EHanShi singleton = new EHanShi();
//私有化构造函数,防止外部实例化
private EHanShi(){
}
/**
*提供一个全局方法,获取实例
*/
public static EHanShi getInstance(){
return singleton;
}
}
5.懒汉模式
懒汉模式是声明一个静态对象,并且在用户第一次调用getInstance时进行初始化,这种方式执行
效率比较低,典型的以时间换取空间。
public class LanHanShi {
private static LanHanShi singleton = null;
//私有化构造函数,防止外部实例化
private LanHanShi(){
}
//加上同步锁synchronized是为了线程安全
public static synchronized LanHanShi getInstance(){
if(singleton == null){
singleton = new LanHanShi();
}
return singleton;
}
}
6.双重检查加锁懒汉式
所谓双重检查加锁机制是指:并不是每次进入getInstance方法都需要同步,
而是先不同步,进入方法过后,先检查实例是否存在,如果不存在,才进入下面的
同步块,这是第一重检查。进入同步块过后,再次检查实例是否存在,如果不存在,
就在同步的情况下创建一个实例,这时第二重检查。
/* 要使用关键字volatile:被volatile修饰的变量的值,将不会被本地线程缓存,所以对该变量的读写都是直接操作共享内存,从而去确保多个线程能正确处理该变量。*/
public class DoubleCheckLockLanHanShi(){
private volatile static DoubleCheckLockLanHanShi singleton = null;
private DoubleCheckLockLanHanShi(){
}
public static DoubleCheckLockLanHanShi getInstance(){
//第一重判断
if(singleton == null){
synchronized(DoubleCheckLockLanHanShi.class){
//第二重判断
if(singleton == null){
singleton = new DoubleCheckLockLanHanShi();
}
}
}
return singleton;
}
}
7.Lazy initialization holder
更好的单例实现模式,既实现了延迟加载,又实现了线程安全,这里使用到java类级内部类,和多
线程缺省同步锁的知识。
类级内部类指的是:有static修饰的成员式内部类。没有static修饰的成
员式内部类称为对象级内部类,类级内部类可以定义静态的方法,相对于其外部类的成员,只有在
第一次被使用时才会被加载。
线程缺省同步锁:在某些情况下,jvm已经隐含的为您执行同步。由静态初始化器(在静态字段上或static{}块中的初始化器)初始化数据时访问final字段时,在创建线程之前创建对象时,线程可以看见它将要处理的对象时。
public class BetterSingleton{
private static class SingletonHolder{
//静态初始化器,由JVM来保证线程安全
private static BetterSingleton singleton = new BetterSingleton();
}
//私有化构造函数,防止外部实例化
private BetterSingleton(){
}
public static BetterSingleton getSingletonInstance(){
return SingletonHolder.singleton;
}
}
8.枚举类型的单例
public enum EnumSingleton{
//定义一个枚举类型的元素,它就代表类EnumSingleton的一个实例
singleton;
//单例可以有自己的操作
public void singletonOperation(){
//功能处理
}
}
9.使用容器实现单例模式
public class SingletonManager {
private static Map<String,Object> objMap = new HashMap<String,Object>();
private SingletonManager(){
}
public static void registerService(String key,Object instance){
if(!objMap.containsKey(key){
objMap.put(key,instance);
}
}
public static Object getInstance(String key) {
return objMap.get(key);
}
}
10.单例模式的优点
1)由于单例模式在内存中只有一个实例,减少了内存开销,特别是一个对象需要频繁创建、销毁时,而且创建于销毁时性能又无法优化,单例模式的优势就非常明显。
2)由于单例模式只生成一个实例,所以减少了系统的性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其它依赖对象时,则可以通过在用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。
3)单例模式可以避免对资源的多重占用,例如一个写文件操作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作。
4)单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如,可以设计一个单例类,负责所有数据表的映射处理。
11.单例模式缺点
1)单例模式一般没有接口,扩展很困难,若要扩展,除了修改代码基本没有第二种途径可以实现。
2)单例模式持有Context,那么很容易引发内存泄漏,此时需要注意传递给单例对象的Context最好是ApplicationContext。