创建型设计模式-1.单例设计模式

  • 阿里云国际版折扣https://www.yundadi.com

  • 阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

    创建型设计模式-1.单例设计模式

    创建型设计模式核心目的就是给我们提供了一系列全新的创建对象的方式方法

    一、简介

    1.简述

    单例设计模式Singleton Design Pattern一个类只允许创建一个对象或实例那这个类就是一个单例类这种设计模式就叫作单例设计模式简称单例模式。

    • 当一个类被设计为单例类时它只有一个全局访问点通过该访问点可以获取该类的唯一实例。
    • 使用单例模式的主要目的是在整个应用程序中共享一个共享资源或控制对唯一实例的访问。
    • 通过限制对象的创建单例模式确保系统中只有一个实例存在并提供对该实例的全局访问从而避免了重复创建对象的开销。

    2.使用场景

    单例模式适用于以下场景

    • 当只需要一个实例来协调某个操作时如线程池、日志记录器或缓存管理器。
    • 当需要控制资源使用并确保全局访问时如数据库连接池或文件管理器。
    • 当一个对象需要被频繁地访问但创建和销毁实例的开销较大时。

    3.优点和缺点

    单例模式的优点包括

    • 提供全局访问点方便对实例进行统一的管理和控制。
    • 减少了重复创建对象的开销提高了系统的性能。
    • 确保在整个应用程序中只有一个实例存在避免了资源的浪费和冲突。

    然而单例模式也有一些潜在的缺点

    • 单例模式可能会引入全局状态增加了程序的复杂性和耦合度。
    • 单例模式的扩展性有限如果需要修改单例类的实现可能需要修改大量的代码。
    • 单例模式在多线程环境下需要注意线程安全性的问题需要确保实例的创建和访问是线程安全的。

    总之单例模式是一种强大的设计模式适用于需要共享资源或控制实例访问的情况。通过合理地使用单例模式可以提高应用程序的性能、可维护性和可扩展性。

    4.使用步骤

    单例设计模式是一种创建型设计模式它确保一个类只有一个实例并提供一个全局访问点来访问该实例。以下是创建一个单例的一般步骤

    1. 私有化构造函数将类的构造函数私有化这样其他类就无法直接实例化该类的对象。

    2. 创建静态私有实例变量在类的内部创建一个静态私有变量用于存储类的唯一实例。

    3. 创建静态公有方法提供一个公有的静态方法用于获取类的实例。这个方法会在内部检查是否已经存在实例如果存在则直接返回实例如果不存在则创建一个实例并返回。

    下面是一个简单的例子来说明单例设计模式的步骤

    public class Singleton {
        private static Singleton instance;  // 静态私有实例变量
    
        private Singleton() {
            // 私有化构造函数
        }
    
        public static Singleton getInstance() {
            if (instance == null) {  // 检查是否已经存在实例
                instance = new Singleton();  // 创建实例
            }
            return instance;  // 返回实例
        }
    }
    

    在上述代码中构造函数被声明为私有的确保其他类无法直接实例化Singleton类的对象。instance变量是一个静态私有变量用于存储类的唯一实例。getInstance()方法是一个静态公有方法用于获取Singleton类的实例。在该方法中首先检查instance是否为null如果为null则创建一个新的实例否则直接返回已有的实例。

    使用单例模式时其他类可以通过调用Singleton.getInstance()来获取Singleton类的实例并且每次调用都会返回同一个实例。这样可以确保在整个程序中只有一个Singleton对象存在。

    二、案例

    1.饿汉式单例

    单例设计模式-饿汉式

    饿汉式是一种简单直接的单例模式实现方式。**在类加载的时候就创建了唯一的实例对象并在整个生命周期中保持不变。**因此它也被称为“急切”创建实例的方式。

    public class HgySingleton {
        private static final HgySingleton INSTANCE = new HgySingleton();
    
        private HgySingleton() {
        }
    
        public static HgySingleton getInstance() {
            return INSTANCE;
        }
    }
    

    2.懒汉式单例

    单例设计模式-懒汉式

    懒汉式是一种延迟实例化的方式在第一次访问获取实例的时候才会创建对象。这种方式在实例创建之前没有额外的开销但需要考虑多线程环境下的线程安全问题

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

    3.双重检查锁单例

    单例设计模式-双重检查锁

    双重检查锁是对懒汉式的改进通过在获取实例时进行双重检查确保只在实例未创建时才进行同步操作。这样在多线程环境下可以保持高性能并且线程安全。

    public class DclSingleton {
        private static volatile DclSingleton INSTANCE;
    
        private DclSingleton() {
    
        }
    
        public static DclSingleton getInstance() {
            if (INSTANCE == null) {
                synchronized (DclSingleton.class) {
                    if (INSTANCE == null) {
                        INSTANCE = new DclSingleton();
                    }
                }
            }
            return INSTANCE;
        }
    }
    

    4.内部类单例

    单例设计模式-静态内部类

    静态内部类方式是一种延迟加载实例的方式它利用了Java类加载机制中的静态内部类不会在外部类加载时被加载的特性。当第一次获取实例时静态内部类才会被加载并创建实例。

    public class InnerSingleton {
    
        private InnerSingleton() {
    
        }
    
        public static InnerSingleton getInstance() {
            return SingletonHolder.INSTANCE;
        }
    
        private static class SingletonHolder {
            private static final InnerSingleton INSTANCE = new InnerSingleton();
        }
    }
    

    5.枚举类单例

    单例设计模式-枚举类

    枚举类方式是Java中最简洁、高效且线程安全的单例模式实现方式。枚举类的实例是在枚举常量被第一次访问时创建的而且它天生就是线程安全的。

    public class EnumSingleton {
        private EnumSingleton() {
            // 私有构造函数
        }
    
        public static EnumSingleton getInstance() {
            return SingletonEnum.INSTANCE.getInstance();
        }
    
        private static enum SingletonEnum {
            INSTANCE;
    
            private EnumSingleton instant;
    
            // 在枚举常量中初始化单例实例
            private SingletonEnum() {
                instant = new EnumSingleton();
            }
    
            public EnumSingleton getInstance() {
                return instant;
            }
        }
    }
    

    枚举可以抵挡常规的反射攻击是因为Java语言规范对枚举类进行了特殊处理。当使用反射尝试访问枚举类的私有构造函数时会抛出IllegalAccessException异常阻止了对枚举类进行实例化的尝试。

    这种反射抵御是通过Java语言规范的设计来实现的。在枚举类中构造函数被默认为私有的不允许外部代码直接访问。当使用反射时如果尝试通过Constructor.newInstance()方法来调用枚举类的私有构造函数Java会在内部检查是否为枚举类并抛出异常。

    此外枚举常量在枚举类被加载时就被实例化并且是在枚举类初始化阶段完成的。这意味着在第一次访问枚举类时所有的枚举常量都会被创建而且无法再通过反射来创建新的枚举常量实例。

    综上所述由于枚举类的特殊处理和枚举常量的提前实例化枚举可以有效地抵挡常规的反射攻击确保枚举类的单例特性和唯一性。这也是为什么使用枚举实现单例模式是一种安全可靠的方式。然而需要注意的是反射仍然可以访问枚举类的其他成员变量和方法只是无法通过反射创建新的枚举常量实例。

    单例设计模式-反射和序列化

    使用常规的单例模式实现可能会受到反射和序列化的影响而产生多个实例。为了防止这种情况需要在单例类中做特殊处理确保在使用反射和序列化时依然保持单例特性。

    public class ReflectSerializableSingleton implements Serializable {
        private static volatile ReflectSerializableSingleton INSTANCE;
    
        private ReflectSerializableSingleton() {
            if (INSTANCE != null) {
                throw new RuntimeException("实例【" + this.getClass().getName() + "】已经存在该实例只允许实例化一次");
            }
        }
    
        public static ReflectSerializableSingleton getInstance() {
            if (INSTANCE == null) {
                synchronized (ReflectSerializableSingleton.class) {
                    if (INSTANCE == null) {
                        INSTANCE = new ReflectSerializableSingleton();
                    }
                }
            }
            return INSTANCE;
        }
        // readResolve()方法可以用于替换从流中读取的对象在进行反序列化时会尝试执行readResolve方法并将返回值作为反序列化的结果而不会克隆一个新的实例保证jvm中仅仅有一个实例存在
        public Object readResolve(){
            return singleton;
        }
    }
    

    三、总结

    1.应用

    单例模式的应用

    1. jdk中有一个类的实现是一个标准单例模式->Runtime类该类封装了运行时的环境。每个 Java 应用程序都有一个 Runtime 类实例使应用程序能够与其运行的环境相连接。 一般不能实例化一个Runtime对象应用程序也不能创建自己的 Runtime 类实例但可以通过 getRuntime 方法获取当前Runtime运行时对象的引用。
    2. Mybaits中的org.apache.ibatis.io.VFS使用到了单例模式。VFS就是Virtual File System的意思mybatis通过VFS来查找指定路径下的资源。查看VFS以及它的实现 类不难发现VFS的角色就是对更“底层”的查找指定资源的方法的封装将复杂的 “底层”操作封装到易于使用的高层模块中方便使用者使用。

    2.存在问题

    单例存在的问题

    1. 无法支持面向对象编程单例模式的构造方法被私有化这导致无法将单例类作为其他类的父类限制了继承和多态的特性。这意味着在面对未来的需求变化时扩展性受到限制。如果需要创建类似但略有差异的单例就需要重新创建一个相似且大部分功能相同的单例类造成代码冗余。

    2. 极难的横向扩展单例模式只允许存在一个实例对象如果未来需要创建更多实例以满足不同需求就必须修改源代码。这违反了开闭原则增加新的实例需要对现有代码进行修改导致扩展困难。例如在数据库连接池中可能从一个连接变成需要多个连接。

    3. 不同作用范围的单例

      1. 线程级别的单例每个线程都拥有自己的单例实例线程之间互不干扰。
      2. 容器范围的单例在容器中管理单例实例容器可以管理多个单例对象并控制其生命周期。
      3. 日志中的多例在日志记录中可能需要根据不同的上下文创建多个实例来记录不同的日志信息。

    请注意尽管单例模式存在一些缺点但在某些情况下仍然是有用的设计模式。在使用单例模式时需要仔细考虑其适用性和潜在的问题并根据具体情况做出权衡决策。

  • 阿里云国际版折扣https://www.yundadi.com

  • 阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6