单例模式的理论与实践

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

本文实践代码仓库:https://github.com/goSilver/my_practice

一、定义

单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点以访问该实例。单例模式常用于需要共享资源或控制某个唯一资源的场景,例如数据库连接、线程池等。

二、作用

单例模式可以确保在整个应用程序中只有一个对象实例存在,从而方便地共享资源、管理状态或控制某些操作。

从业务概念上,有些数据在系统中只应该保存一份,就比较适合设计为单例类。比如,系统的配置信息类。除此之外,我们还可以使用单例解决资源访问冲突的问题。

三、实现

要实现一个单例,我们需要关注的点无外乎下面几个:

  • 构造函数需要是 private 访问权限的,这样才能避免外部通过 new 创建实例;
  • 考虑对象创建时的线程安全问题;
  • 考虑是否支持延迟加载
  • 考虑 getInstance() 性能是否高(是否加锁)。

3.1 饿汉式

饿汉式的实现方式比较简单。在类加载的时候,instance 静态实例就已经创建并初始化好了,所以,instance 实例的创建过程是线程安全的。不过,这样的实现方式不支持延迟加载(在真正用到IdGenerator 的时候,再创建实例)。

如果实例资源初始化时间长、占用资源多,那么最好是采用饿汉式,将耗时的初始化操作提前到程序启动时就完成,避免程序在运行中发生崩溃。

public class Hungry {

    private final AtomicLong id = new AtomicLong(0);

    /**
     * 类加载时就初始化
     */
    private static final Hungry instance = new Hungry();

    private Hungry() {
    }

    public static Hungry getInstance() {
        return instance;
    }

    public long getId() {
        return id.incrementAndGet();
    }
}

3.2 懒汉式

懒汉式相对于饿汉式的优势是支持延迟加载。这种实现方式会导致频繁加锁、释放锁,以及并发度低等问题,频繁的调用会产生性能瓶颈。

public class Lazy {

    private final AtomicLong id = new AtomicLong(0);
    private static Lazy instance;

    private Lazy() {
    }

    /**
     * 获取实例的方法被synchronized关键字修饰
     * @return 实例
     */
    public static synchronized Lazy getInstance() {
        if (instance == null) {
            instance = new Lazy();
        }
        return instance;
    }

    public long getId() {
        return id.incrementAndGet();
    }
}

3.3 双重检查

双重检测实现方式既支持延迟加载、又支持高并发的单例实现方式。**只要 instance 被创建之后,再调用 getInstance() 函数都不会进入到加锁逻辑中。**所以,这种实现方式解决了懒汉式并发度低的问题。

public class DoubleCheck {
    private final AtomicLong id = new AtomicLong(0);
    /**
     * volatile关键字修饰
     * CPU 指令重排序可能导致在 IdGenerator 类的对象被关键字 new 创建并赋值给 instance 之后,还没来得及初始化(执行构造函数中的代码逻辑),就被另一个线程使用了。
     * 这样,另一个线程就使用了一个没有完整初始化的 IdGenerator 类的对象。
     * 要解决这个问题,我们只需要给 instance 成员变量添加 volatile 关键字来禁止指令重排序即可。
     */
    private static volatile DoubleCheck instance;

    private DoubleCheck() {
    }

    /**
     * 双重检查
     * @return 实例
     */
    public static DoubleCheck getInstance() {
        if (instance == null) {
            // 只有第一次才会执行到这里,此处为类级别锁
            synchronized (DoubleCheck.class) {
                if (instance == null) {
                    instance = new DoubleCheck();
                }
            }
        }
        return instance;
    }

    public long getId() {
        return id.incrementAndGet();
    }
}

3.4 静态内部类

利用 Java 的静态内部类来实现单例。这种实现方式,既支持延迟加载,也支持高并发,实现起来也比双重检测简单。

public class StaticInnerClass {
    private AtomicLong id = new AtomicLong(0);
    private StaticInnerClass() {
    }

    /**
     * 静态内部类
     * SingletonHolder 是一个静态内部类,当外部类 IdGenerator 被加载的时候,并不会创建 SingletonHolder 实例对象。
     * 只有当调用 getInstance() 方法时,SingletonHolder 才会被加载,这个时候才会创建 instance。
     * instance 的唯一性、创建过程的线程安全性,都由 JVM 来保证。所以,这种实现方法既保证了线程安全,又能做到延迟加载。
     */
    private static class SingletonHolder {
        private static final StaticInnerClass instance = new StaticInnerClass();
    }

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

    public long getId() {
        return id.incrementAndGet();
    }
}

3.5 枚举

最简单的实现方式,基于枚举类型的单例实现。这种实现方式通过 Java 枚举类型本身的特性,保证了实例创建的线程安全性和实例的唯一性。

public enum Enumm {
    /**
     * 实例
     */
    INSTANCE;
    private final AtomicLong id = new AtomicLong(0);
    public long getId() {
        return id.incrementAndGet();
    }
}

四、总结

4.1 单例存在哪些问题?

  1. 单例对 OOP 特性的支持不友好

一旦你选择将某个类设计成到单例类,也就意味着放弃了继承和多态这两个强有力的面向对象特性,也就相当于损失了可以应对未来需求变化的扩展性。

  1. 单例会隐藏类之间的依赖关系

通过构造函数、参数传递等方式声明的类之间的依赖关系,我们通过查看函数的定义,就能很容易识别出来。但是,单例类不需要显示创建、不需要依赖参数传递,在函数中直接调用就可以了。如果代码比较复杂,这种调用关系就会非常隐蔽。在阅读代码的时候,我们就需要仔细查看每个函数的代码实现,才能知道这个类到底依赖了哪些单例类。

  1. 单例对代码的扩展性不友好

单例类只能有一个对象实例。如果未来某一天,我们需要在代码中创建两个实例或多个实例,那就要对代码有比较大的改动。

  1. 单例对代码的可测试性不友好

单例模式的使用会影响到代码的可测试性。如果单例类依赖比较重的外部资源,比如 DB,我们在写单元测试的时候,希望能通过 mock 的方式将它替换掉。而单例类这种硬编码式的使用方式,导致无法实现 mock 替换。

  1. 单例不支持有参数的构造函数

单例不支持有参数的构造函数,比如我们创建一个连接池的单例对象,我们没法通过参数来指定连接池的大小。

4.2 单例有什么替代解决方案?

为了保证全局唯一,除了使用单例,我们还可以用静态方法来实现。不过,静态方法这种实现思路,并不能解决我们之前提到的问题。如果要完全解决这些问题,我们可能要从根上,寻找其他方式来实现全局唯一类了。比如,通过工厂模式、IOC 容器(比如 Spring IOC 容器)来保证,由程序员自己来保证(自己在编写代码的时候自己保证不要创建两个类对象)。

有人把单例当作反模式,主张杜绝在项目中使用。我个人觉得这有点极端。模式没有对错,关键看你怎么用。如果单例类并没有后续扩展的需求,并且不依赖外部系统,那设计成单例类就没有太大问题。对于一些全局的类,我们在其他地方 new 的话,还要在类之间传来传去,不如直接做成单例类,使用起来简洁方便。文章来源地址https://www.toymoban.com/news/detail-657606.html

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

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

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

相关文章

  • 设计模式篇(Java):单例模式

    上一篇:设计模式篇(Java):前言(UML类图、七大原则) 所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)。 构造器私有化 (防止 new ) 类的内部创建对象 向外暴露一个静

    2024年02月11日
    浏览(59)
  • Java设计模式---单例 工厂 代理模式

    单例模式是设计模式中的一种,属于创建型模式。在软件工程中,单例模式确保一个类只有一个实例,并提供一个全局访问点。这种模式常用于那些需要频繁实例化然后引用,且创建新实例的开销较大的类,例如数据库连接池、缓存管理等。 意图 :保证一个类仅有一个实例

    2024年01月24日
    浏览(50)
  • java设计模式-单例

    单例模式是一种创建型设计模式,它可以保证一个类只有一个实例,并提供全局访问点。单例模式在实际开发中经常使用,可以避免多个实例引起的资源浪费和同步问题。常见的java实现方式有多种。 饿汉式单例模式是指在类加载时就创建了单例对象,因此在调用时不需要再

    2024年01月18日
    浏览(51)
  • Java与设计模式(4):单例模式

    单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供全局访问点来访问该实例。 在单例模式中,类的构造函数被私有化,从而禁止外部直接实例化该类。通过一个静态方法或静态变量来控制类的实例化过程,并返回同一个实例。 单例模式的特点包括: 单一

    2024年02月12日
    浏览(67)
  • java设计模式-单例模式(Singleton)

    单例模式(Singleton)就是一个类只能有一个实例,自行实例化,并向系统提供这一实例,这个类就是单例类。单例模式的特点: 一个类只能有一个实例; 单例类自己实例化; 单例类给其它对象提供这个单一实例。 资源管理类经常被设计为单例模式,例如管理属性文件的类。

    2024年02月15日
    浏览(37)
  • Java设计模式之单例模式

    定义:保证一个类仅有一个实例,并提供一个全局访问点 类型:创建型 想确保任何情况下都绝对只有一个实例 例如:线程池,数据库连接池一般都为单例模式 单例模式优点 在内存中只有一个实例,减少内存开销 可以避免对资源的多重占用 设置全局访问点,严格控制访问

    2024年02月02日
    浏览(71)
  • Java设计模式(八)— 单例模式3

    单例模式之静态内部类 单例模式之枚举方式 单例模式之JDK源码分析 Hello,小伙伴们,欢迎来到柚子的博客~让我们一起成长吧o( ̄▽ ̄)ブ 提示:以下是本篇文章正文内容,下面案例可供参考 代码如下(示例): 类被装载的时候,类里面的静态内部类也是会被装载的,而且线

    2024年02月09日
    浏览(67)
  • 03-JAVA设计模式-单例模式详解

    单例模式(Singleton Pattern)是设计模式中的一种,它确保一个类仅有一个实例,并提供一个全局访问点来访问该实例。这种设计模式属于创建型模式,它提供了一种创建对象的最佳方式。 单例模式的应用场景十分广泛,主要涉及需要频繁使用某个对象而又不想重复创建的情况

    2024年04月13日
    浏览(54)
  • Java——单例设计模式

    设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。设计模式免去我们自己再思考和摸索。就像是经典的棋谱,不同的棋局,我们用不同的棋谱、“套路”。 经典的设计模式共有23种。每个设计模式均是特定环境下特定问题的

    2024年02月11日
    浏览(45)
  • java基础之设计模式(单例模式,工厂模式)

    是一种编码套路 单例模式 一个类只能创建一个实例 饿汉式 直接创建唯一实例 缺点: 有可能浪费空间 懒汉式 在获取实例是创建唯一对象 缺点: 线程效率慢 懒汉式-进阶版 在懒汉式的基础上,利用同步代码块结合二次校验提高执行效率 工厂模式 是一种底层技术,通常用于底层框

    2024年01月18日
    浏览(43)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包