设计模式学习(二)单例模式

目录

 

定义

懒汉式与饿汉式

应用场景

优缺点

总结


定义

其定义如下:

Ensure a class has only one instance, and provide a global point of access to it.(确保某一个类 只有一个实例,而且自行实例化并向整个系统提供这个实例。)

再来查看一个通用类图如下:

Singleton类提供一个静态Singleton变量并实例化自己,将无参构造函数给私有化,避免外部类可以new一个新对象出来,然后提供一个getSingleton()方法提供给到外部,该方法里只提供自己已实例化好的对象,将这个通用类再对应到上面的定义,就应该基本能理解啥是单例模式了。再将抽象转换三个特点理解如下:

  • 自己实例化一个实例
  • 将构造方法私有化,避免别人可以实例化
  • 创建public方法提供已实例化的这个对象

懒汉式与饿汉式

啥是饿汉式,啥是懒汉式,用最简单的话来说,饿汉式是应用程序一启动,该实例对象就已被实例化,懒汉式是被调用的时候才被实例化,如果一直都不被调用,那么该实例不会被实例化,好处还是有一点,那么分别再通过代码来认识下吧。

 static class HungrySingleton{
        // 自实例化
       private static final HungrySingleton hungrySingleton = new HungrySingleton();

       // 将构造方法私有化
        private HungrySingleton(){}

        // 提供获取实例的方法
        public static HungrySingleton getInstance(){
            return hungrySingleton;
        }

    }

这里看到的是饿汉式,应用进程启动后该对象就会被实例化。

static class LazySingleton{

        private static LazySingleton lazySingleton = null;

        // 将构造方法私有化
        private LazySingleton(){}

        // 提供获取实例的方法
        public static synchronized LazySingleton getInstance(){
            if(lazySingleton == null){
                lazySingleton = new LazySingleton();
            }
            return lazySingleton;
        }
    }

这里看到的就是懒汉式,该模式有个缺点,每次get实例,都会被加锁变成同步,在高并发场景下式比较影响效率的,而且其实实例化只会一次,理论上加锁一次即可,那么有什么处理方法呢?

static class LazySingleton2{

        /**
         * 要添加volatile关键字,该关键字有两个作用,1、保证线程操作能够互相看到,2、保证按顺序执行
         * singleton = new Singleton();
         * 该语句非原子操作,实际是三个步骤。
         * 1.给singleton分配内存;
         * 2.调用 Singleton 的构造函数来初始化成员变量;
         * 3.将给singleton对象指向分配的内存空间(此时singleton才不为null);
         * 由于Java 平台内存模型的原因,允许无序执行,所以可能执行的顺序为1、3、2,如果先执行了3那么另外一个
         * 线程判断不为null,返回的该实例其实式未初始化成员变量的,那么就会导致不可用,所以加volatile
         * 可以避免这个问题
         */
        private static volatile LazySingleton2 lazySingleton2 = null;

        // 将构造方法私有化
        private LazySingleton2(){}

        // 提供获取实例的方法
        public static synchronized LazySingleton2 getInstance(){
            if(lazySingleton2 == null){
                synchronized (LazySingleton2.class){
                    if(lazySingleton2 == null){
                        lazySingleton2 = new LazySingleton2();
                    }
                }
            }
            return lazySingleton2;
        }
    }

这里看到的就是懒汉式的变种,双重校验,每次先判断是否为null,如果为null才会加synchronized同步处理,后续实例化后不会再加锁处理,高并发场景下也不会受到影响,但需要注意的式需要加volatile关键字,具体原因见注释。

还有静态内部类实例方式,枚举类方式,这里就不再多做介绍。

应用场景

在一个系统中,要求一个类有且仅有一个对象,如果出现多个对象就会出现“不良反应”,可以采用单例模式,具体的场景如下:

  • 要求生成唯一序列号的环境
  • 在整个项目中需要一个共享访问点或共享数据,例如一个Web页面上的计数器,可以不用把每次刷新都记录到数据库中,使用单例模式保持计数器的值,并确保是线程安全的
  • 频繁访问数据库或文件的对象,如配置文件对象
  • 需要定义大量的静态常量和静态方法(如工具类)的环境,可以采用单例模式(当然,也可以直接声明为static的方式)
  • 应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加
  • Windows的Task Manager(任务管理器)就是很典型的单例模式(这个很熟悉吧),想想看,是不是呢,你能打开两个windows task manager吗?

优缺点

优点: 
    1.在单例模式中,活动的单例只有一个实例,对单例类的所有实例化得到的都是相同的一个实例。这样就 防止其它对象对自己的实例化,确保所有的对象都访问一个实例 
    2.单例模式具有一定的伸缩性,类自己来控制实例化进程,类就在改变实例化进程上有相应的伸缩性
    3.提供了对唯一实例的受控访问
    4.由于在系统内存中只存在一个对象,因此可以节约系统资源,当需要频繁创建和销毁的对象时单例模式无疑可以提高系统的性能
    5.允许可变数目的实例(单例的拓展)
    6.避免对共享资源的多重占用
缺点: 
    1.不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态
    2.由于单利模式中没有抽象层,因此单例类的扩展有很大的困难
    3.单例类的职责过重,在一定程度上违背了“单一职责原则”
    4.滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失
使用注意事项: 
    1.使用时不能用反射模式创建单例,否则会实例化一个新的对象 
    2.使用懒单例模式时注意线程安全问题 
    3.饿单例模式和懒单例模式构造方法都是私有的,因而是不能被继承的,有些单例模式可以被继承(如登记式模式

总结

在了解了单例模式基本概念后,我们可以在读源码的过程中,有意识的去注意哪些用到了单例模式,然后思考为什么要用?通过这种方式去进一步熟悉它。在工作过程中尝试使用它,多多思考不同场景什么时候该用,什么时候不该用。最后熟练后忘记它就是融入骨子里的东西了。

猜你喜欢

转载自blog.csdn.net/he_cha_bu/article/details/114156034