美文网首页
设计模式之单例模式

设计模式之单例模式

作者: BrightLight | 来源:发表于2019-01-24 00:07 被阅读0次

    一、介绍:

    单例模式是应用最广的模式之一;在应用这个模式时,单例对象的类必须保证只有一个实例的存在;许多时候,整个系统只需要拥有一个全局对象,这样有利于我们协调系统整体的行为;如在一个应用中,应该只有一个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 ;

    相关文章

      网友评论

          本文标题:设计模式之单例模式

          本文链接:https://www.haomeiwen.com/subject/arsyhqtx.html