设计模式之单例模式

2019-01-24  本文已影响0人  BrightLight

一、介绍:

单例模式是应用最广的模式之一;在应用这个模式时,单例对象的类必须保证只有一个实例的存在;许多时候,整个系统只需要拥有一个全局对象,这样有利于我们协调系统整体的行为;如在一个应用中,应该只有一个ImageLoader实例,这个ImageLoder中有含有线程池、缓存系统、网络请求等,很消耗资源,很消耗资源,因此,没有理由让他构造多个实例。这种不能自由构造对象的情况,其实就是单例模式的使用场景;

二、定义

确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例;

三、单例模式使用的场景

确保某个类有且只有一个对象的场景,避免产生多个对象消耗过多的资源,或者某种类型的对象只应该有且只有一个。例如:创建一个对象需要消耗的资源过多,如要访问IO和数据库等资源,这时就要考虑使用单例模式;

四、单例模式的关键点

1、类的构造函数不对外开放,一般为Private;
2、通过一个静态方法或者枚举返回单例类对象;
3、确保单例类对象有且只有一个,特别是在多线程的环境下;
4、确保单例类对象在反序列化时不会重新构建对象;

五、单例类的简单实例 : 饿汉模式,在声明静态对象时就进行初始化

例:一个公司只有一个CEO,可以有几个VP、无数个员工,但CEO只有一个:

/**普通员工*/
public class Staff{
    public void work(){
       //工作
  }
}
/** 副总裁 */
public class VP extends Staff{
    @override
    public void work{
    //副总裁工作,管理下面的经理
  }
}
/**  CEO 饿汉单例模式*/
public class CEO extends Staff{
    private static final CEO mCeo  = new CEO();
    //构造私有函数
    private CEO(){};
    //定义公有的静态函数,对外暴露获取单例对象的接口
    public static CEO getCeo(){
              retrun mCeo ;
      }
      @override
      public void work(){
              //CEO工作管理VP
      }
}
/**公司类*/
public class Company{
    private List<Staff> allStaffs = new ArrayList<>();
     public void addStaff(Staff per){
            allStaffs.add(per);
      }
      public void showAllStaffs(){
          for(Staff staff:allStaffs){
                system.out.println(“Obj:” + staff.toString);
           }
      }
}
/**测试*/
public class SingletonDemo {
    public static void main(String[] args) {
        Company mCompany = new Company();      //一个公司
        Staff mStaff1 = CEO.getCeo();    //一个CEO,单例
        Staff mStaff2 = CEO.getCeo();
        mCompany.addStaff(mStaff1);
        mCompany.addStaff(mStaff1);
        //通过new 创建VP
        Staff vp1 = new VP();
        Staff vp2 = new VP();
        mCompany.addStaff(vp1);
        mCompany.addStaff(vp2);
        //通过new创建staff
        Staff staff1 = new Staff() ;
        Staff staff2 = new Staff();
        Staff staff3 = new Staff();
        mCompany.addStaff(staff1);
        mCompany.addStaff(staff2);
        mCompany.addStaff(staff3);
        mCompany.showAllStaff();
    }
}
测试结果如下:
Obj : sl.com.designmodedemo.a_singleton.CEO@28d93b30
Obj : sl.com.designmodedemo.a_singleton.CEO@28d93b30
Obj : sl.com.designmodedemo.a_singleton.VP@1b6d3586
Obj : sl.com.designmodedemo.a_singleton.VP@4554617c
Obj : sl.com.designmodedemo.a_singleton.Staff@74a14482
Obj : sl.com.designmodedemo.a_singleton.Staff@1540e19d
Obj : sl.com.designmodedemo.a_singleton.Staff@677327b6

从结果中可以看出:CEO类是由CEO.getCeo()获取的实例对象,而这个CEO的对象是静态对象,并且在声明的时候就已经初始化了,就保证了这个CEO对象的唯一性,从输出结果也可以发现,两次CEO的输出对象都是一样的;而其他Staff、VP对象都是不同的,
这种实现方式的核心在于:将CEO类的构造方法私有化,使得外部程序不能通过构造函数获取实例,而CEO则通过一个静态方法返回静态对象;

六、单例模式的其他实现方式

一、懒汉模式:懒汉模式是声明一个静态对象,并且在用户第一次调用getInstance时进行初始化

public class Singleton {
    private static Singleton instance ;
    private Singleton(){}
    public static synchronized Singleton getInstance(){
        if (instance == null){
            instance = new Singleton() ;
        }
        return  instance ;
    }
}

优点:单例只有在使用的时候才会被实例化,在一定程度上节约了资源;
缺点:第一次加载时需要及时进行实例化,反应稍慢,最大的问题是每次调用getInstance都进行同步,造成不必要的同步开销;这种模式一般不建议使用;
二、Double Check Lock (DCL)实现单例:

public class Singleton {
    private static Singleton instance ;
    private Singleton(){}
    public static Singleton getInstance(){
        if (instance == null){          //判空,避免不必要的同步
            synchronized (Singleton.class){
                if (instance == null){      //判空,创建实例
                    instance = new Singleton() ;
                }
            }
        }
        return instance;
    }
}

优点:第一次执行getInstance时才会进行实例化,效率高,又能够保证线程安全,且单例对象初始化后调用getInstance不进行同步锁。
缺点:第一次执行稍慢,也由于Java内存模型的原因偶尔会失败,在高并发的环境下也有一定的缺陷,虽然发生概率很小;
DCL模式是使用最多的单例模式,他能够在需要时才实例化单例对象,并且在绝大数场景下保证单例对象的唯一性,除非你的代码在并发场景比较复杂或者低于JDK6版本下使用,否则,这种方式一般能够满足需求;
三、静态内部类单例模式

public class Singleton {
//    静态内部类单例模式
    private Singleton(){}
    public static Singleton getInstance(){
        return SingletonHolder.sInstance ;
    }
    private static class SingletonHolder{
        private static final Singleton sInstance = new Singleton() ;
    }
}

当第一次加载Singleton类时并不会初始化sInstance,只有在第一次调用Singleton的getInstance方法才会导致sInstance被初始化。因此第一次调用getInstance方法会导致虚拟机加载SingletonHolder类,这种方式不仅能够确保线程安全,也能够抱枕单例对象的唯一性,同时也延迟了单例的实例化。所以这是推荐使用的单例模式实现方式;
四、枚举单例

public enum  SingletonEnum{
    INSTANCE;
    public void doSomething(){
        System.out.println("do something");
    }

五、使用容器实现单例模式

public class SingletonManager{
    private static Map<String,Object> 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);
    }
}

总结

单例模式是使用频率很高的模式,但是,由于在客户端通常没有高并发的情况,因此,选择哪种实现方法并不会有太大的影响,即使如此,出于效率考虑,一般也是使用DCL方式和内部类单例的实现形式;
优点
1、单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁的创建和销毁时,而且创建和销毁的性能又无法优化,单例模式的优势就非常明显;
2、单例模式只生产一个实例,减少了系统的新能开销,当一个对象的场所需要较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决;
3、单例模式可以避免对资源的多重占用,例如一个写文件操作,由于只有一个实例存在内存中,避免对一个资源文件的同时写操作;
4、单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如,可以设计一个单例类,负责所有的数据表的映射管理;
缺点
1、单例模式一般没有借口,扩展困难,如要扩展,除了修改代码基本上没有第二种途径可以实现;
2、单例对象如果持有Context,那么很容易引发内存泄漏,此时需要注意传给到单例对象的Context最好是Application Context ;

上一篇下一篇

猜你喜欢

热点阅读