创建型设计模式06-单例模式

这篇具有很好参考价值的文章主要介绍了创建型设计模式06-单例模式。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

🧑‍💻作者:猫十二懿

❤️‍🔥账号:CSDN 、掘金 、个人博客 、Github

🎉公众号:猫十二懿

单例模式

单例模式是一种创建型设计模式,它的目的是 确保一个类只有一个实例,并提供一个全局访问点来访问该实例 。在单例模式中,类自身负责创建自己的唯一实例,并确保在系统中只有一个实例存在。

1、单例模式介绍

单例模式(Singleton),保证一个类仅有一个实例,并提供一个访问它的全局访问点。

通常我们可以让一个全局变量使得一个对象被访问,但它不能防止你 实例化多个对象。一个最好的办法就是,让类自身负责保存它的唯一实例。 这个类可以保证没有其他实例可以被创建,并且它可以提供一个访问该实例 的方法。

1.1 单例模式结构图

创建型设计模式06-单例模式

Singleton类,定义一个GetInstance操作,允许客户访问它的唯一实例。GetInstance是一个静态方法,主要负责创建自己的唯一实例。

/**
 * @author Shier
 * CreateTime 2023/5/14 9:23
 * 单例模式
 */
public class Singleton {
    private static Singleton singleton;

    /**
     * 无参构造 防止外部代码利用new来实例化的可能
     */
    private Singleton() {
    }

    /**
     * 只能通过此途径获取Singleton实例
     * @return
     */
    public static Singleton getInstance() {
        // 为空,则创建实例
        if (singleton == null) {
            singleton = new Singleton();
        }
        // 不为空,则已创建,直接返回实例
        return singleton;
    }
}

客户端:

/**
 * @author Shier
 * CreateTime 2023/5/14 9:27
 */
public class SingletonClient {
    public static void main(String[] args) {
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        if (instance1 == instance2) {
            System.out.println("两个是同一个实例对象");
        }
    }
}

最终得到的:“两个是同一个实例对象”,因此单例模式保证了可以被唯一实例化。当然单例模式不止这些好处。下面再来细说

2、具体例子说明

假设我们有一个流水号生成器,用于在系统中生成唯一的流水号。这个流水号生成器需要保证在整个系统中只有一个实例存在,以确保生成的流水号是唯一的。也就是类似于我们在外面吃饭点菜呀什么的,买单后,商家会给你一个单号,到你了,就让你去拿属于你的饭菜。

2.1 不使用单例模式

首先,我们创建一个名为SerialNumberGenerator的类,用于生成流水号:

/**
 * @author Shier
 * CreateTime 2023/5/14 9:30
 * 流水号例子
 */
public class SerialNumberGenerator {
    private int serialNumber;

    /**
     * 外界可以实例化SerialNumberGenerator
     */
    public SerialNumberGenerator() {
        serialNumber = 0;
    }

    public int generateSerialNumber() {
        serialNumber++;
        return serialNumber;
    }
}

现在,我们可以在不同的地方创建多个SerialNumberGenerator的实例并尝试生成流水号:

/**
 * @author Shier
 * CreateTime 2023/5/14 9:31
 * 客户端
 */
public class Main {
    public static void main(String[] args) {
        SerialNumberGenerator generator1 = new SerialNumberGenerator();
        int serialNumber1 = generator1.generateSerialNumber();
        System.out.println("流水号1: " + serialNumber1);

        SerialNumberGenerator generator2 = new SerialNumberGenerator();
        int serialNumber2 = generator2.generateSerialNumber();
        System.out.println("流水号2: " + serialNumber2);
    }
}

结果:

流水号1: 1
流水号2: 1

不使用单例模式存在的问题:

  1. 生成重复流水号:由于我们可以在不同的地方创建多个SerialNumberGenerator实例,每个实例都会有自己的serialNumber变量。这可能导致在不同的实例中生成相同的流水号,从而产生重复的流水号。
  2. 没有共享状态:每个SerialNumberGenerator实例都会维护自己的serialNumber变量,这导致每个实例生成的流水号不会互相影响。这意味着如果我们想要全局唯一的流水号,就需要在各个地方共享同一个SerialNumberGenerator实例,而不是创建多个实例。
  3. 难以管理和控制:在多个地方分别创建和管理SerialNumberGenerator实例会增加代码的复杂性。我们需要确保在需要流水号的每个地方都使用同一个实例,否则会出现上述问题。这增加了代码维护的难度,并且容易在系统的不同部分中出现错误使用的情况。
  4. 重复的流水号:如果多个地方都可以创建流水号生成器的实例,那么每个实例都有可能生成相同的流水号,导致重复的流水号出现。
  5. 资源浪费:如果每个部分都创建自己的流水号生成器实例,那么会造成资源的浪费,例如每个实例都需要独立维护和生成流水号的状态。
  6. 一致性问题:如果多个流水号生成器实例同时生成流水号,可能会导致不同实例生成的流水号在顺序上出现混乱。

2.2 使用单例模式

使用单例模式的实现方式有多种,这里我将使用线程安全的懒汉式单例模式,具体实现如下:

懒汉式单例模式下面再介绍

/**
 * @author Shier
 */
public class SerialNumberGenerator1 {
    private static volatile SerialNumberGenerator instance;
    private int serialNumber;

    /**
     * 防止直接实例化SerialNumberGenerator1
     */
    private SerialNumberGenerator1() {
        serialNumber = 0;
    }

    /**
     * 获取SerialNumberGenerator1的唯一方式
     * @return
     */
    public static SerialNumberGenerator getInstance() {
        if (instance == null) {
            synchronized (SerialNumberGenerator.class) {
                if (instance == null) {
                    instance = new SerialNumberGenerator();
                }
            }
        }
        return instance;
    }

    /**
     * 生成的流水号
     * @return
     */
    public int generateSerialNumber() {
        synchronized (SerialNumberGenerator.class) {
            serialNumber++;
            return serialNumber;
        }
    }
}

在这个实现中,我们使用了一个静态变量instance来保存SerialNumberGenerator的唯一实例。getInstance()方法返回这个实例,如果它不存在,则会创建一个新的实例。generateSerialNumber()方法负责生成流水号,使用synchronized关键字来确保线程安全。

现在,我们可以在不同的地方调用SerialNumberGenerator.getInstance().generateSerialNumber()来生成流水号,并确保它们是唯一的:

/**
 * @author Shier
 */
public class Main1 {
    public static void main(String[] args) {
        int serialNumber1 = SerialNumberGenerator1.getInstance().generateSerialNumber();
        System.out.println("流水号 1: " + serialNumber1);

        int serialNumber2 = SerialNumberGenerator1.getInstance().generateSerialNumber();
        System.out.println("流水号 2: " + serialNumber2);
    }
}

通过使用单例模式,我们解决了不使用单例模式存在的问题:

  1. 确保唯一性:SerialNumberGenerator的唯一实例可以在系统中被多次调用,但它们都会引用同一个实例,因此可以保证生成的流水号是唯一的。
  2. 共享状态:通过使用单例模式,所有生成的流水号共享同一个serialNumber变量,从而避免了重复生成流水号的问题。
  3. 管理和控制:由于只有一个SerialNumberGenerator实例存在于系统中,因此我们不必在各个地方管理和控制实例的创建和使用,这降低了代码复杂性并减少了出错的可能性。

使用单例模式来实现流水号生成器类,可以确保生成的流水号是唯一的,避免资源浪费,并保证生成的流水号顺序一致。所有部分都可以通过访问流水号生成器的全局访问点来获取唯一的流水号,从而简化了流水号的生成和管理。

3、单例模式实现方式

在上面例子当中使用到了懒汉式单例模式,下面我们再来看看单例模式的实现方式:

单例模式的实现方式有多种,以下是几种常见的实现方式:

  1. 懒汉式(Lazy Initialization):在首次使用时创建实例。懒汉式单例模式可以延迟实例化,避免不必要的资源消耗。但需要考虑线程安全性,可以通过加锁或使用双重检查锁定等方式来确保线程安全。
  2. 饿汉式(Eager Initialization):在类加载时即创建实例。饿汉式单例模式在类加载时就创建实例,因此不存在线程安全问题。但可能会提前创建实例,造成资源浪费。
  3. 静态内部类(Static Inner Class):利用类的静态内部类来实现单例模式。这种方式可以实现延迟加载和线程安全,且对外部类的加载不产生影响。
  4. 枚举(Enum):利用枚举类型实现单例模式。枚举类型的实例是线程安全的,并且保证只有一个实例存在。
  5. 双重检查锁定(Double-Checked Locking):结合懒汉式和加锁机制,通过双重检查来实现延迟加载和线程安全。
  6. 容器(Container):使用容器来存储实例,每次获取实例时先从容器中查找,如果不存在则创建并添加到容器中。这种方式适用于需要管理多个单例对象的情况。

以上是常见的几种单例模式的实现方式。每种实现方式都有其适用的场景和特点,选择哪种方式取决于具体的需求和设计考虑。同时,需要注意线程安全性和性能等因素,确保实现的单例模式符合要求。

下面再说说最常用的懒汉式和饿汉式单例模式实现

3.1 懒汉式单例模式

在第一次使用时才创建实例,称为懒汉式。实现方式是在类内部定义一个私有的静态变量作为实例,然后提供一个公有的静态方法来获取该实例。在方法内部判断实例是否已经存在,如果存在则直接返回,如果不存在则创建一个新的实例并返回。(我就是很懒,你不用我,我就不管你,当你用我的时候才回去给你提供需要的东西)

public class Singleton {
    private static Singleton instance;
    // 私有构造函数
    private Singleton() {}
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

在懒汉式单例模式中,我们将单例对象的实例化放在静态方法getInstance()中。当首次调用getInstance()时,会检查实例是否已经存在。如果实例为null,表示还未创建,则进行实例化。这种方式实现了延迟加载,只有在需要使用单例对象时才会创建它。

需要注意的是,在多线程环境下,懒汉式单例模式需要考虑线程安全性。上述代码的实现并未考虑线程安全,可能会导致多个线程同时创建实例。为了解决这个问题,可以在getInstance()方法中添加线程同步机制,如使用synchronized关键字或者双重检查锁定等方式,来确保在多线程环境下仅有一个实例被创建。

下面再来说多线程单例模式

3.2 饿汉式单例模式

饿汉式单例模式: 在类加载时就创建实例,称为饿汉式。实现方式是在类内部定义一个私有的静态变量,并直接创建实例赋值给它,然后提供一个公有的静态方法来获取该实例。(快要饿死了,很需要吃东西,所以说菜一上来就立马开吃,不管你三七二十四)

public class Singleton {
    private static final Singleton instance = new Singleton();
    // 私有构造函数
    private Singleton() {
    }
    public static Singleton getInstance() {
        return instance;
    }
}

在饿汉式单例模式中,我们将单例对象的实例化放在静态常量中,并且将构造函数设为私有,防止外部代码创建实例。在类加载时,静态常量instance就会被创建,并且通过公共的静态方法getInstance()返回该实例。由于实例在类加载时就被创建,因此可以保证单例的唯一性。

3.3 多线程的单例模式

3.3.1 同步方法

同步方法(Synchronized Method):在getInstance()方法上使用synchronized关键字,确保在同一时间只有一个线程可以进入方法,从而避免并发创建多个实例。这种方式简单易行,但 可能存在性能问题,因为每次调用getInstance()都需要进行同步。

public class Singleton {
    private static Singleton instance;
    // 私有构造函数
    private Singleton() {
    }
    public synchronized static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
3.3.2 双重检查锁定

双重检查锁定(Double-Checked Locking):使用双重检查锁定机制,在getInstance()方法内使用synchronized块,只在实例为null时才进行同步,避免了每次调用都进行同步的开销。这种方式在多线程环境下能够保证线程安全性,同时也具有较好的性能。

public class Singleton {
    private static volatile Singleton instance;
    // 私有构造函数
    private Singleton() {
    }
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

需要注意的是,这种方式需要将instance变量声明为volatile,以确保在多线程环境下的可见性和禁止指令重排序。

3.3.3 静态内部类

静态内部类(Static Inner Class):利用类的静态内部类来实现单例模式。静态内部类在首次使用时加载,且只加载一次,因此保证了线程安全性和延迟加载。

public class Singleton {
    private Singleton() {
        // 私有构造函数
    }

    private static class SingletonHolder {
        private static final Singleton instance = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHolder.instance;
    }
}

在多线程环境下,上述实现方式都能够确保只有一个实例被创建,并提供线程安全的访问。选择哪种方式取决于具体的需求和性能要求。此外,还可以结合其他技术,如使用枚举类型实现单例,因为枚举类型的实例是线程安全且唯一的。

需要注意的是,在某些情况下,可能需要权衡线程安全和性能之间的取舍。在高并发环境下,可以考虑使用其他的并发控制方式,如使用锁、使用线程安全的并发容器等来实现单例模式。

4、单例模式总结

单例模式的核心思想是将类的实例化过程控制在一个特定的范围内,以确保只有一个实例被创建并且全局可访问。这种模式在需要共享资源或避免重复创建相同对象的场景中非常有用。

以下是单例模式的一般实现步骤:

  1. 类的构造函数设为私有,防止外部代码直接创建实例。
  2. 类内部创建一个私有的静态变量来保存单例实例
  3. 提供一个公共的静态方法来获取单例实例,该方法负责实例的创建和返回。
  4. 在获取实例的方法中,需要考虑线程安全性,确保在多线程环境下只有一个实例被创建。

单例模式的优点:

  1. 全局唯一性:确保只有一个实例存在,全局范围内可以访问该实例。
  2. 节省资源:避免重复创建相同的对象,节省了系统资源。
  3. 简化调用:通过单例模式可以将实例的管理和控制集中处理,简化了代码的使用和调用方式。

单例模式缺点:

  1. 隐藏依赖关系:单例模式将对象的创建和使用耦合在一起,使得对象之间的依赖关系难以识别和管理。其他组件或类可能会依赖单例对象,从而增加了代码的复杂性和耦合度。
  2. 可测试性降低:由于单例对象的全局性质,可能导致测试困难。在单元测试中,如果依赖于单例对象的方法或类,可能会难以模拟和替换实例,从而影响测试的可靠性和可维护性。
  3. 线程安全问题:在多线程环境下,需要特别注意单例模式的线程安全性。如果实现不当,可能导致多个线程同时创建实例,从而破坏了单例的唯一性。需要额外的同步措施来确保线程安全,这可能会引入性能开销。
  4. 限制扩展性:单例模式通常是通过将实例化逻辑封装在类内部来实现的,这限制了对类的扩展性。如果需要扩展功能或修改实例化逻辑,可能需要修改单例类的代码,从而影响其他代码的稳定性和可维护性。
  5. 对象生命周期管理困难:由于单例对象的生命周期长于其他对象,可能会导致内存泄漏问题。一旦单例对象被创建,它将一直存在于内存中,直到应用程序结束或显式销毁。在某些情况下,可能难以控制和管理单例对象的生命周期。

因此,应谨慎使用单例模式,并根据具体需求和设计考虑选择适当的实现方式,以确保单例的正确性和适用性。

单例模式使用场景:文章来源地址https://www.toymoban.com/news/detail-473878.html

  1. 全局资源共享:当系统中有多个模块或对象需要共享同一个资源时,可以使用单例模式来管理该资源,确保只有一个实例存在。例如,数据库连接池、线程池、日志管理器等。
  2. 配置信息管理:当需要全局访问和管理配置信息时,可以使用单例模式来存储和获取配置信息。这样可以避免重复读取配置文件,提高性能和效率。
  3. 日志记录器:在需要记录系统日志的场景下,使用单例模式可以确保日志记录器的唯一性和全局访问性。通过单例模式,可以集中管理日志记录器的配置和输出。
  4. 缓存管理:在需要管理全局缓存的场景下,可以使用单例模式来实现缓存管理器。单例模式确保只有一个缓存管理器实例存在,避免重复创建和维护多个缓存实例。
  5. 线程池管理:在多线程环境中,使用单例模式来管理线程池可以确保线程池的唯一性和可控性。通过单例模式,可以统一管理线程池的创建、销毁和任务调度。
  6. GUI应用程序中的窗口管理:在图形用户界面(GUI)应用程序中,使用单例模式来管理窗口对象可以确保每个窗口只有一个实例,方便对窗口进行控制和管理。
    例如,数据库连接池、线程池、日志管理器等。
  7. 配置信息管理:当需要全局访问和管理配置信息时,可以使用单例模式来存储和获取配置信息。这样可以避免重复读取配置文件,提高性能和效率。
  8. 日志记录器:在需要记录系统日志的场景下,使用单例模式可以确保日志记录器的唯一性和全局访问性。通过单例模式,可以集中管理日志记录器的配置和输出。
  9. 缓存管理:在需要管理全局缓存的场景下,可以使用单例模式来实现缓存管理器。单例模式确保只有一个缓存管理器实例存在,避免重复创建和维护多个缓存实例。
  10. 线程池管理:在多线程环境中,使用单例模式来管理线程池可以确保线程池的唯一性和可控性。通过单例模式,可以统一管理线程池的创建、销毁和任务调度。
  11. GUI应用程序中的窗口管理:在图形用户界面(GUI)应用程序中,使用单例模式来管理窗口对象可以确保每个窗口只有一个实例,方便对窗口进行控制和管理。

到了这里,关于创建型设计模式06-单例模式的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 学习笔记-设计模式-创建型模式-单例模式

    一个类只有一个实例,并提供一个全局访问此实例的点,哪怕多线程同时访问。 单例模式主要解决了 一个全局使用的类被频繁的创建和消费 的问题。 单例模式的案例场景 数据库的连接池不会反复创建 spring中一个单例模式bean的生成和使用 在我们平常的代码中需要设置全局

    2024年02月08日
    浏览(53)
  • 【地铁上的设计模式】--创建型模式:单例模式(五)--枚举单例

    什么是枚举单例 枚举单例是指使用枚举类型来实现单例模式,它是单例模式中最简单、最安全的一种实现方式。在枚举类型中定义的枚举值只会被实例化一次,即保证了全局唯一的实例,而且实现简单、线程安全、防止反射攻击、支持序列化等。 如何实现枚举单例 实现枚举

    2023年04月25日
    浏览(81)
  • 设计模式-创建型模式(单例、工厂、建造、原型)

    设计模式:软件设计中普遍存在(反复出现)的各种问题,所提出的解决方案。 面向对象三大特性:封装、继承、多态。 面向对象设计的SOLID原则: (1)开放封闭原则:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情

    2024年02月08日
    浏览(53)
  • c#设计模式-创建型模式 之 单例模式

    目录 前言: 优点: 缺点: 饿汉式(静态变量方式) 懒汉式(线程不安全) 懒汉式(双重检查锁定) 推荐方式Lazy 总结: 这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供 了一种访问其唯一的对象的方式,可以直接访问,

    2024年02月13日
    浏览(39)
  • GO设计模式——4、单例模式(创建型)

    目录 单例模式(Singleton Pattern)   优缺点 使用场景 饿汉式和懒汉式单例模式         单例模式(Singleton Pattern)是一个类 只允许创建一个 对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式。单例模式的要点有三个:一是

    2024年02月05日
    浏览(49)
  • 【Java 设计模式】创建型之单例模式

    在软件开发中,单例模式是一种常见的设计模式, 它确保一个类只有一个实例,并提供一个全局访问点 。单例模式在需要控制某些资源,如数据库连接池、线程池等共享资源的情况下非常有用。在本文中,我们将介绍 Java 设计模式中的单例模式,了解其实现方式、使用场景

    2024年01月18日
    浏览(55)
  • 深入理解设计模式-创建型之单例模式

    如果有些数据在系统中应该且只能保存一份,那就应该设计为单例类。 如:配置类:在系统中,我们只有一个配置文件,当配置文件被加载到内存之后,应该被映射为一个唯一的【配置实例】,此时就可以使用单例,当然也可以不用。 全局计数器:我们使用一个全局的计数

    2024年02月12日
    浏览(59)
  • C++设计模式创建型之单例模式

    一、概述         单例模式也称单态模式,是一种创建型模式,用于创建只能产生一个对象实例的类。例如,项目中只存在一个声音管理系统、一个配置系统、一个文件管理系统、一个日志系统等,甚至如果吧整个Windows操作系统看成一个项目,那么其中只存在一个任务管理

    2024年02月14日
    浏览(49)
  • Java23种设计模式-创建型模式之单例模式

    单例模式 (Singleton Pattern):通过单例模式的方法创建的 类在当前进程中只有一个实例 (根据需要,也有可能一个线程中属于单例,如:仅线程上下文内使用同一个实例),该类负责 创建自己的对象 ,同时 确保只有单个对象 被创建。 注 : 1、单例类 只能 有 一个实例 。

    2024年04月26日
    浏览(54)
  • 《golang设计模式》第一部分·创建型模式-01-单例模式(Singleton)

    指目标类(Class)只有一个实例对象(Object),并且向使用该对象的客户端提供访问单例的全局方法。 保证类只有一个实例 有方法能让外部访问到该实例 懒汉式 在第一次调用单例对象时创建该对象,这样可以避免不必要的资源浪费 饿汉式 在程序启动时就创建单例对象,这

    2024年02月14日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包