当前位置: 首页 > 科技观察

写一个小的单例模式有那么多种方法?

时间:2023-03-18 16:49:50 科技观察

单例模式应该是设计模式中最容易理解和使用最多的模式,也是面试中被问到最多的模式。1、单例模式的定义单例模式是指在任何情况下,一个类中绝对只有一个实例,并提供一个全局访问点。2、单例模式的应用场景单例模式的应用非常广泛,比如数据库中的连接池,J2EE中的ServletContext和ServletContextConfig,Spring框架中的ApplicationContext等等。但是在Java中,单例模式也可以保证一个JVM中只有一个唯一的实例。单例模式的应用场景主要包括以下几个方面:当需要频繁创建一些类时,使用单例可以减轻系统的内存压力,减少GC(垃圾收集);有些类在创建实例时,需要占用的资源较多,或者实例化过程耗时较长,经常被使用;当有频繁访问数据库或文件的对象时;当一些控制硬件级的操作,或者本应是系统单一控制逻辑的操作时,不允许出现多个实例,否则游戏结束;3.单例模式的优缺点3.1单例模式的优点单例模式可以保证内存中只有一个实例对象,从而减少内存开销;单例模式可以避免资源的多次占用;单例模式设置全局访问点,可以优化和共享资源访问;3.2单例模式的缺点难以扩展,因为单例模式通常是没有接口的,如果你想扩展,只能修改之前的代码,所以单例模式违背了开放原则关闭;调试难,因为在并发测试中,单例模式不利于代码调试,单例中的代码不执行,无法模拟生成新的对象;违反了单一职责原则,因为单例模式的业务代码通常写在一个类中,如果功能设计不合理,很容易违反单一职责原则;4.单例模式的实现方法及优缺点4.1单例模式的Hungry-style实现4.1.1Hungry-style的标准写法Singleton类称为单例类,采用内部初始化一次的方式实现,隐藏构造方法,并提供一个全局访问点。/***msJava**@DescriptionUniversal单例模式写法*@Date2021-01-23*/publicclassSingleton{/***内部初始化一次*/privatestaticfinalSingletoninstance=newSingleton();/***隐藏构造方法*/privateSingleton(){}/***提供一个全局的访问点**@returnSingleton*/publicstaticSingletongetInstance(){returninstance;}}上面的饿汉单例写法会在类初始化的时候进行初始化,并创建对象,绝对线程-safe,因为此时线程在出现之前就已经被实例化了,所以不会有访问安全问题。4.1.2Hungry-style静态阻塞机制Hungry-style静态阻塞机制还有一种实现方式,就是静态阻塞机制,如下代码所示:/***msJava**@DescriptionSingletonmodeHungry-stylestaticmechanismimplementation*@Date2021-01-23*/publicclassHungryStaticSingleton{privatestaticfinalHungryStaticSingletonhungrySingleton;//静态代码块类加载时,初始化static{hungrySingleton=newHungryStaticSingleton();}/***私有构造函数*/privateHungryStaticSingleton(){}/***提供了一个全局访问点*@return*/publicstaticHungryStaticSingletongetInstance(){returnhungrySingleton;}}下面分析一下这种写法,可以清楚的看到对象是在类加载的时候实例化的,所以这样一来,就会如结果,单例对象的数量是不确定的,这会在系统初始化时造成大量的内存浪费。再说了,你用不着也未必用,它还占空间,俗称“占厕不拉屎”。4.2单例模式的lazy-style实现为了解决hungry-style单例写法可能带来的内存浪费问题,这里对lazy-style单例写法进行分析。如下代码所示:/***msJava**@DescriptionSingletonModeLazySingletonImplementation*@Date2021-01-23*/publicclassLazySimpleSingleton{privatestaticLazySimpleSingletonlazySingleton=null;/***privateconstructor*/privateLazySimpleSingleton(){}/***提供一个全局访问点**@return*/publicstaticLazySimpleSingletongetInstance(){if(lazySingleton==null){lazySingleton=newLazySimpleSingleton();}returnlazySingleton;}}这种实现的好处是只使用对象就会只有在没有内存浪费问题的情况下才会被初始化,但是在多线程环境下会存在线程安全问题。我们可以使用synchronized关键字,将全局访问点方法变成synchronized方法,这样线程安全的问题就可以解决了。代码如下:/***msJava**@DescriptionSingleton模式Lazy单例实现同步修改*@Date2021-01-23*/publicclassLazySimpleSingleton{privatestaticLazySimpleSingletonlazySingleton=null;/***私有构造函数*/privateLazySimpleSingleton(){}/***提供全局访问点**@return*/publicsynchronizedstaticLazySimpleSingletongetInstance(){if(lazySingleton==null){lazySingleton=newLazySimpleSingleton();}returnlazySingleton;}}不过这样虽然解决了线程安全的问题,如果线程数量急剧增加,使用同步锁会导致大量线程阻塞,从而大幅降低系统性能。4.3单例模式下doublecheck的实现在上述代码上进一步优化,代码如下:/***msJava**@Descriptionsingletonmodelazy-doublechecksingletonimplementation*@Date2021-01-23*/publicclassLazyDoubleCheckSingleton{//volatile关键字修饰privatevolatilestaticLazyDoubleCheckSingletonlazySingleton;/***私有构造函数*/privateLazyDoubleCheckSingleton(){}/***提供全局访问点**@return*/publicstaticLazyDoubleCheckSingletongetInstance(){//这里先判断是否阻塞if(lazySingleton==null){synchronized(LazyDoubleCheckSingleton.class){//判断是否重新创建实例if(lazySingleton==null){lazySingleton=newLazyDoubleCheckSingleton();}}}returnlazySingleton;}}()方法,第二个线程也可以调用,但是第一个线程执行synchronized的时候,第二个线程会发现阻塞,但是此时的阻塞是getInstance()的内部阻塞。4.4单例模式的静态内部类实现双检测锁的单例模式虽然解决了线程安全和性能的问题,但毕竟涉及到加锁操作,或多或少都会影响性能。下面就来分享一些比较优雅的吧。单例模式实现,如下代码所示:/***msJava**@Description单例模式静态内部类单例实现*@Date2021-01-23*/publicclassLazyStaticInnerClassSingleton{//在构造函数中抛出异常是否真的合适?privateLazyStaticInnerClassSingleton(){if(LazyHolder.INSTANCE!=null){thrownewRuntimeException("不允许多个实例");}}//static保证这个方法不会被覆盖默认不会加载内部类privatestaticclassLazyHolder{privatestaticfinalLazyStaticInnerClassSingletonINSTANCE=newLazyStaticInnerClassSingleton();}}5.总结单例模式面试几乎是必须的!