Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >单例模式(上)

单例模式(上)

作者头像
WindCoder
发布于 2020-04-28 02:31:16
发布于 2020-04-28 02:31:16
44500
代码可运行
举报
文章被收录于专栏:WindCoderWindCoder
运行总次数:0
代码可运行

1. 什么是

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

2. 为何用

2.1 处理资源访问冲突

单例模式相对于之前类级别锁的好处是,不用创建那么多 Logger 对象,一方面节省内存空间,另一方面节省系统文件句柄(对于操作系统来说,文件句柄也是一种资源,不能随便浪费)。

2.2 表示全局唯一类

从业务概念上,如果有些数据在系统中只应保存一份,那就比较适合设计为单例类。

大部分情况下,我们在项目中使用单例,都是用它来表示一些全局唯一类,比如配置信息类、连接池类、ID 生成器类。

单例模式书写简洁、使用方便,在代码中,我们不需要创建对象,直接通过类似 IdGenerator.getInstance().getId() 这样的方法来调用就可以了。

3. 怎样实现

相关文章 Java设计模式学习笔记—单例模式(上) Java设计模式学习笔记—单例模式(下) 并发学习笔记11-双重检查锁定与延迟初始化

3.1 饿汉模式

  • 饿汉式的实现方式,在类加载的期间,就已经将 instance 静态实例初始化好了,所以,instance 实例的创建是线程安全的。
  • 这样的实现方式不支持延迟加载实例
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class IdGenerator1 {
    private AtomicLong id = new AtomicLong(0);
    private static final IdGenerator1 instantce = new IdGenerator1();

    private IdGenerator1(){}


    public static IdGenerator1 getInstance() {
        return instantce;
    }

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

3.2 懒汉模式

懒汉式相对于饿汉式的优势是支持延迟加载

这种实现方式使用了synchronized 锁,导致函数的并发度很低,即会导致频繁加锁、释放锁,以及并发度低等问题,频繁的调用会产生性能瓶颈,此种情况下该方法便不可取。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class IdGenerator2 {
    private AtomicLong id = new AtomicLong(0);
    private static IdGenerator2 instance;

    private IdGenerator2(){}


    public static synchronized IdGenerator2 getInstance() {
        if (instance == null) {
            instance = new IdGenerator2();
        }

        return instance;
    }

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

}

3.3 双重检测

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class IdGenerator3 {

    private AtomicLong id = new AtomicLong(0);
    // private static  IdGenerator3 instance;
    private static volatile IdGenerator3 instance;

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

    public long getId() {
        return id.incrementAndGet();
    }
}
3.3.1 volatile关键字的作用

指令重排序,可能会导致 IdGenerator 对象被 new 出来,并且赋值给 instance 之后,还没来得及初始化(执行构造函数中的代码逻辑),就被另一个线程使用了。

故给 instance 成员变量加上 volatile 关键字,禁止指令重排序,从而解决该问题。

然而设计模式之美中有这样一段话:

只有很低版本的 Java 才会有这个问题。我们现在用的高版本的 Java 已经在 JDK 内部实现中解决了这个问题(解决的方法很简单,只要把对象 new 操作和初始化操作设计为原子操作,就自然能禁止重排序)。

关于这段话在网上暂未找到相关依据,《深入理解JVM虚拟机(第三版)》中涉及双重检测的部分也没看到有关于此的相关改动,实力有限,暂且存疑。

3.5 静态内部类

利用 Java 的静态内部类来实现单例。这种实现方式

  • 支持延迟加载
  • 支持高并发
  • 实现起来也比双重检测简单
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class IdGenerator4 {

    private AtomicLong id = new AtomicLong(0);

    private IdGenerator4(){}

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

    public static IdGenerator4 getInstance(){

        return SingletonHolder.instance;
    }

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

3.6 枚举

最简单的实现方式,基于枚举类型的单例实现。

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public enum IdGenerator5 {
    INSTANCE;
    private AtomicLong id = new AtomicLong(0);
    public long getId() {
        return id.incrementAndGet();
    }
}

4. 单例存在的问题

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

IdGenerator 的使用方式违背了基于接口而非实现的设计原则,也就违背了广义上理解的 OOP 的抽象特性

当某天需要不同业务采用不同的ID生成算法,为了该需求,需要修改所有用到IdGenerator 类的地方,这样代码的改动就会比较大。

单例对继承、多态特性的支持也不友好。因为从理论上来讲,单例类也可以被继承、也可以实现多态,只是实现起来会非常奇怪,会导致代码的可读性变差。不明白设计意图的人,看到这样的设计,会觉得莫名其妙。

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

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

通过构造函数、参数传递等方式声明的类之间的依赖关系,通过查看函数的定义,就能很容易识别出来。

单例类不需要显示创建、不需要依赖参数传递,在函数中直接调用就可以了。如果代码比较复杂,这种调用关系就会非常隐蔽。在阅读代码的时候, 需要仔细查看每个函数的代码实现,才能知道这个类到底依赖了哪些单例类。

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

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

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

单例类这种硬编码式的使用方式,导致无法实现 mock 替换。

如果单例类持有成员变量(比如 IdGenerator 中的 id 成员变量),那它实际上相当于一种全局变量,被所有的代码共享。在编写单元测试的时候,还需要注意不同测试用例之间,修改了单例类中的同一个成员变量的值,从而导致测试结果互相影响的问题。

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

  • 第一种解决思路是:使用这个单例类的时候,要先调用 init() 函数传递参数,然后才能调用 getInstance() 方法。
  • 第二种解决思路是:将参数放到 getIntance() 方法中。
  • 第三种解决思路是:将参数放到另外一个全局变量中,单例里面的值既可以通过静态常量来定义,也可以从配置文件中加载得到。

参考资料

设计模式之美

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
创建型-Singleton
what 单例设计模式(Singleton Design Pattern)理解起来非常简单。一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式。
acc8226
2022/05/17
4680
单例模式的常用实现方式
单例模式属于最常用的设计模式,Java中有很多实现单例模式的方式,各有其优缺点 实现方式对比 单例实现方式 线程安全 延迟加载 性能 复杂度 饿汉式 安全 不支持 好 简单 懒汉式 安全 支持 差 一般 双重检测 安全 支持 好 复杂 静态内部类 安全 支持 好 简单 枚举 安全 不支持 好 最简单 实现方式示例 实现一个ID生成器的单例 饿汉式: 不支持延迟加载 public class IdGenerator { private AtomicLong id = new AtomicLong(0
十毛
2021/12/03
3300
01.单例模式设计思想
本文详细介绍了单例模式的设计思想及其应用。首先阐述了单例模式的基本概念、特点与定义,并探讨其适用场景与常见问题。接着深入分析了为何使用单例模式,包括处理资源访问冲突和表示全局唯一类。随后详细讲解了几种常见的单例实现方式,如饿汉式、懒汉式、双重检查锁定、静态内部类及枚举等,对比了各自优缺点。最后讨论了单例模式可能带来的问题,如对OOP不友好、隐藏依赖关系、扩展性差等,并提出了一些替代解决方案。文章内容丰富,适合希望深入了解单例模式及其应用的读者。
杨充
2024/10/14
1940
深入理解几种单例模式的实现方式
饿汉式的单例实现方式就是说在类加载的时候就已经创建并初始化好了,所以实例的创建过程是线程安全的
测试轩
2020/04/28
3030
深入理解几种单例模式的实现方式
学习单例模式引发的思考
学习单例引发的思考 单例存在哪里问题? 1.单例对oop的支持不友好 OOP 四大特性: 封装 继承 多态 抽象 而单例这种设计模式对于其中的抽象 继承 多态 都支持的不好 为什么这么说呢? 我们先来
Java宝典
2021/01/14
5650
单例模式(下)
在上篇 《单例模式(上)》一文中介绍了单例定义、使用场景、实现方式以及不足,本篇继续整理针对不足的解决方案以及唯一性的相关讨论与实现等。
WindCoder
2020/04/28
1K0
设计模式 | 创建型 | 单例模式
一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式。
被水淹没
2023/02/25
4730
设计模式 | 创建型 | 单例模式
01.创建型:单例设计模式
创建型:单例设计模式1目录介绍01.单例模式介绍02.单例模式定义03.单例使用场景04.思考几个问题05.为什么要使用单例06.处理资源访问冲突07.表示全局唯一类01.单例模式介绍单例模式是应用最广的模式也是最先知道的一种设计模式,在深入了解单例模式之前,每当遇到如:getInstance()这样的创建实例的代码时,我都会把它当做一种单例模式的实现。单例模式特点构造函数不对外开放,一般为private通过一个静态方法或者枚举返回单例类对象确保单例类的对象有且只有一个,尤其是在多线程的环境下确保单例类对象
杨充
2022/09/07
3900
一起学习设计模式--01.单例模式
单例模式是创建型模式的一种,是创建型模式中最简单的设计模式 用于创建那些在软件系统中独一无二的对象。虽然单例模式很简单,但是它的使用频率还是很高的。
独立观察员
2022/12/06
6030
一起学习设计模式--01.单例模式
JAVA设计模式之单例模式
java中单例模式是一种常见的设计模式,单例模式的写法有好几种,这里主要介绍三种:懒汉式单例、饿汉式单例。
秋白
2019/07/02
4370
朋友问我单例模式是什么?
某软件公司承接了一个服务器负载均衡(Load Balance)软件的开发工作,该软件运行在一台负载均衡服务器上,可以将并发访问和数据流量分发到服务器集群中的多台设备上进行并发处理,提高了系统的整体处理能力,缩短了响应时间。由于集群中的服务器需要动态删减,且客户端请求需要统一分发,因此需要确保负载均衡器的唯一性,只能有一个负载均衡器来负责服务器的管理和请求的分发,否则将会带来服务器状态的不一致以及请求分配冲突等问题。如何确保负载均衡器的唯一性是该软件成功的关键,试使用单例模式设计服务器负载均衡器。
千羽
2021/12/29
5320
朋友问我单例模式是什么?
设计模式——单例模式详解
饿汉式:在类加载的时候已经创建好该单例对象。 懒汉式:在需要使用对象的时候才会去创建对象
小尘要自信
2023/10/10
1.3K0
设计模式——单例模式详解
单例模式
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
ruochen
2021/11/25
1630
设计模式之单例模式
  Java Singleton模式主要作用是保证在Java应用程序中,一个类Class只有一个实例存在。 使用Singleton的好处还在于可以节省内存,因为它限制了实例的个数,有利于Java垃圾回收(garbage collection)。   单例模式能够保证一个类仅有唯一的实例,并提供一个全局访问点。单例模式主要有3个特点:
雨落凋殇
2019/12/25
4380
Java设计模式详解----单例模式
前言:软件设计模式( Software Design Pattern ),又称设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。它描述了在软件设计过程中的一些不断重复发生的问题,以及该问题的解决方案。也就是说,它是解决特定问题的一系列套路,是前辈们的代码设计经验的总结,具有一定的普遍性,可以反复使用。
ma布
2024/10/30
1360
Java设计模式详解----单例模式
设计模式篇之一文搞懂如何实现单例模式
大家好,我是小简,这一篇文章,6种单例方法一网打尽,虽然单例模式很简单,但是也是设计模式入门基础,我也来详细讲讲。
JanYork_简昀
2023/03/16
4.7K0
设计模式篇之一文搞懂如何实现单例模式
死磕GOF23之单例模式
Erich Gamma、Richard Helm、Ralph Johnson和John Vlissides 四人合著出版了一本名为Design Patterns - Elements of Reusable Object-Oriented Software(中文译名:设计模式 - 可复用的面向对象软件元素)的书,该书首次提到了软件开发中设计模式的概念。
程序新视界
2022/05/09
3140
详解设计模式:单例模式
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
栗筝i
2022/12/02
4270
设计模式----单例模式详解
单例模式是java中用的比较多的一种设计模式,目的就是让一个应用中对于某个类,只存在唯一的实例化对象。单例模式有很多实现方案,各有利弊,接下来将做详细分析介绍。
叔牙
2020/11/19
5490
设计模式----单例模式详解
JAVA单例模式
在整个应用中,保证一个类只有一个实例,它提供了一个可以访问到它自己的全局访问点(静态方法)。
week
2018/08/23
6890
相关推荐
创建型-Singleton
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验