单例模式详解

简介: 单例模式详解

概述


单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,使用单例模式的类只有一个对象实例。

 

单例应用场景


1.    Windows系统的任务管理器。

2.    Windows系统的回收站。

3.    操作系统的文件系统,一个操作系统只能有一个文件系统。

4.    数据库连接池的设计与实现。

5.    多线程的线程池设计与实现。

6.    Spring中创建的Bean实例默认都是单例。

7.    Java-Web中,一个Servlet类只有一个实例。

8.    等等...

 

 

单例的实现要点


单例模式要求类能够有返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法)。


单例的实现主要是通过以下三个步骤:

1.    将类的构造方法定义为私有方法。这样其他类的代码就无法通过调用该类的构造方法来实例化该类的对象,只能通过该类提供的静态方法来得到该类的唯一实例。

2.    定义一个私有的类的静态实例。

3.    提供一个公有的获取实例的静态方法。

 

单例追求的目标


1.    线程安全。

2.    懒加载。

3.    调用效率高。

 

单例模式的常用写法


常用的单例有以下5种写法,如果还有其他的写法,也基本是从以下5种稍微修改而来,由于内容基本一致,并且可能不是很常用,因此在本文中不再赘述。

 

1.饿汉模式

public class Singleton {
    // 1.饿汉
    private static Singleton instance = new Singleton();
    private Singleton() {}
    public static Singleton getInstance() {
        return instance;
    }
}

饿汉模式,比较常见的一种写法。在类加载的时候就对实例进行初始化,没有线程安全问题;获取实例的静态方法没有使用同步,调用效率高;但是没有使用懒加载,如果该实例从始至终都没被使用过,则会造成内存浪费。

 

总结:线程安全、非懒加载、效率高。

是否推荐:可以使用,但不推荐。

 

2.懒汉模式


public class Singleton {
    // 2.懒汉
    private static Singleton instance;
    private Singleton() {}
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

线程安全的懒汉模式,比较常见的一种写法。在第一次使用的时候才进行初始化,达到了懒加载的效果;由于获取实例的静态方法用synchronized修饰,所以也没有线程安全的问题;但是,这种写法每次获取实例都要进行同步(加锁),因此效率较低,并且可能很多同步都是没必要的。

 

总结:线程安全、懒加载、效率低。

是否推荐:可以使用,但不推荐。

 

注:该模式还有另一种常见写法,就是把getInstance方法上的synchronized去掉,这种方法有线程安全问题,不能使用。

 

3.双重检测机制(DCL)


public class Singleton {
    // 3.双重检测机制
    private volatile static Singleton singleton;
    private Singleton() {}
    public static Singleton getSingleton() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

双重检测机制(双重检查加锁),比较常见的一种写法。在第一次使用的时候才进行初始化,达到了懒加载的效果;在进行初始化的时候会进行同步(加锁),因此没有线程安全问题;并且只有第一次进行初始化才进行同步,因此不会有效率方面的问题。

《Java Concurrency in Practice》作者Brian Goetz在书中提到关于DCL的观点:促使DCL模式出现的驱动力(无竞争同步的执行速度很慢,以及JVM启动时很慢)已经不复存在,因而它不是一种高效的优化措施。延迟初始化占位类模式(静态内部类)能带来同样的优势,并且更容易理解。

 

总结:线程安全、懒加载、效率高。

是否推荐:可以使用。

 

注:该模式还有另一种常见写法,就是把静态实例singleton的volatile修饰去掉,这种方法有线程安全方面的问题,不能使用。在我的另一篇文章有提到这个:volatile关键字详解,这里直接截取该部分内容,请见下面这个例子。

 

 

例子:双重检测机制实现单例(没有volatile修饰)

image.png

这段代码是单例的双重检测机制实现,相信很多人都用过,并且觉得这个代码是没问题的。在大多数情况,这段代码确实没问题,但在极端的情况下,有个隐藏的问题。

 

例子分析:

假设有两个线程同时访问这段代码,此时线程A走到15行开始初始化对象,线程B则刚走到12行进行第一次检测。这时要介绍下15行初始化这行代码,这行代码虽然只有一句话,但是被编译后会变成以下3条指令:

image.png

正常情况下,这3条执行时按顺序执行,双重检测机制就没有问题。但是CPU内部会在保证不影响最终结果的前提下对指令进行重新排序(不影响最终结果只是针对单线程,切记),指令重排的主要目的是为了提高效率。在本例中,如果这3条指令被重排成以下顺序:

image.png

如果线程A执行完1和3,instance对象还未完成初始化,但是已经不再指向null。此时线程B抢占到CPU资源,执行第12行的检测结果为false,则执行第19行,从而返回一个还未初始化完成的instance对象,从而出导致问题出现。要解决这个问题,只需要使用volatile关键字修饰instance对象即可。

 

4.静态内部类(延迟初始化占位类)


public class Singleton {
    // 4.静态内部类
    private static class Holder {
        private static final Singleton INSTANCE = new Singleton();
    }
    private Singleton() {}
    public static final Singleton getInstance() {
        return Holder.INSTANCE;
    }
}

静态内部类(延迟初始化占位类),比较常见的一种写法。JVM将推迟SingletonHolder的初始化操作,直到开始使用这个类时才初始化,并且由于通过一个静态初始化来初始化Singleton,因此不需要额外的同步。当任何一个线程第一次调用getInstance时,都会使SingletonHolder被加载和被初始化,此时静态初始化器将执行Singleton的初始化操作。

 

通过静态初始化来初始化Singleton为什么不需要额外的同步?


在初始器中采用了特殊的方式来处理静态域(或者在静态初始化代码块中初始化的值),并提供了额外的线程安全性保证。静态初始化器是由JVM在类的初始化阶段执行,即在类被加载后并且被线程使用之前。由于JVM将在初始化期间获得一个锁,并且每个线程都至少获取一次这个锁以确保这个类已经加载,因此在静态初始化期间,内存写入操作将自动对所有线程可见。因此无论是在被构造期间还是被引用时,静态初始化的对象都不需要显式的同步。然而,这个规则仅适用于在构造时的状态,如果对象是可变的,那么在读线程和写线程之间仍然需要通过同步来确保随后的修改操作是可见的,以及避免数据破坏。

 

总结:线程安全、懒加载、效率高。

是否推荐:推荐使用、《Java Concurrency in Practice》作者Brian Goetz推荐使用的方式。

 

5.枚举

public enum Singleton {
    INSTANCE;
}

枚举,不是很常见的一种写法。很简洁的一种实现方式,提供了序列化机制,保证线程安全,绝对防止多次实例化,即使是在面对复杂的序列化或者反射攻击的时候。

 

总结:线程安全、非懒加载、效率高。

是否推荐:推荐使用、《Effective Java》作者Joshua Bloch推荐使用的方式。

 

几种方式对比:


image.png

可能有人看了以上表格,觉得枚举有缺点,为什么Joshua Bloch还推荐使用枚举?

这就要提到单例的破解了。普通的单例模式是可以通过反射和序列化/反序列化来破解的,而Enum由于自身的特性问题,是无法破解的。当然,由于这种情况基本不会出现,因此我们在使用单例模式的时候也比较少考虑这个问题。

 

总结:


1.    单例无论是在项目的开发中,还是面试中都是非常常见的,因此需要熟练的掌握好单例的知识。只要记住单例的三个实现要点——私有构造方法、定义静态Singleton实例、暴露实例获取方法,手写这5种实现方式相信都是信手拈来。如果是面试的时候,建议使用静态内部类的实现。


2.    项目中具体使用哪种实现方式可以根据情况而定,个人推荐的方式是:静态内部类和枚举,我自己在项目中常见的也是这两种方式,可能静态内部类会更多一点。另外就是毕竟是两位大神推荐的,还考虑什么,直接用就是了。


—————END—————

相关文章
|
Java Linux 程序员
Linux平台中调试C/C++内存泄漏方法 (腾讯和MTK面试的时候问到的)
Linux平台中调试C/C++内存泄漏方法 (腾讯和MTK面试的时候问到的)
|
8月前
|
人工智能 监控 数据可视化
直观掌控与动态调整:拖拽式任务调度工具如何实现项目流程的即时优化
在数字化协作日益复杂的今天,拖拽式任务调度工具已升级为组织级执行中枢:通过可视化拖拽、动态资源匹配、全链路监控与智能预警,破解任务流转低效、资源错配难题,赋能企业实现灵活编排、实时调整与全局可控的敏捷协同。(239字)
|
JavaScript Docker 容器
使用Docker多阶段构建优化镜像大小
使用Docker多阶段构建优化镜像大小
625 100
|
前端开发 JavaScript 安全
javascript:void(0);用法及常见问题解析
【6月更文挑战第3天】JavaScript 中的 `javascript:void(0)` 用于创建空操作或防止页面跳转。它常见于事件处理程序和超链接的 `href` 属性。然而,现代 web 开发推荐使用 `event.preventDefault()` 替代。使用 `javascript:void(0)` 可能涉及语法错误、微小的性能影响和XSS风险。考虑使用更安全的替代方案,如返回 false 或箭头函数。最佳实践是保持代码清晰、安全和高性能。
10141 0
|
9月前
|
弹性计算 网络协议 小程序
2026年阿里云ECS服务器e实例99元一年ecs.e-c1m1.large性能测评
阿里云99元服务器限时优惠,新老用户均可购买!ECS经济型e实例,2核2G、3M固定带宽、40G云盘,性能稳定,续费同价99元/年,活动持续至2027年。适合个人网站、博客、小程序等轻量应用,不限流量,独立IP,轻松上云。立即前往阿里云活动中心抢购!
864 1
职场新人:怎么做结构化汇报?
文章介绍了结构化汇报的基本逻辑和四种常用逻辑框架,帮助读者提升职场技能。
1814 7
职场新人:怎么做结构化汇报?
|
存储 编解码 网络协议
Android平台GB28181执法记录仪硬件选型和国标技术实现探讨
前几年,我们在做Android平台GB28181设备接入模块的时候,第一个使用场景想到的就是用在公检法应急指挥等场景下的执法记录仪,本篇blog,我们主要围绕Android平台GB28181执法记录仪的硬件选型、设备接入、音视频流配置、流媒体传输、存储和管理、控制与控制中心等方面进行设计,探讨下Android平台GB28181设备接入模块在执法记录仪行业的应用。
854 1
Android平台GB28181执法记录仪硬件选型和国标技术实现探讨
|
Java 大数据 API
Java 流(Stream)、文件(File)和IO的区别
Java中的流(Stream)、文件(File)和输入/输出(I/O)是处理数据的关键概念。`File`类用于基本文件操作,如创建、删除和检查文件;流则提供了数据读写的抽象机制,适用于文件、内存和网络等多种数据源;I/O涵盖更广泛的输入输出操作,包括文件I/O、网络通信等,并支持异常处理和缓冲等功能。实际开发中,这三者常结合使用,以实现高效的数据处理。例如,`File`用于管理文件路径,`Stream`用于读写数据,I/O则处理复杂的输入输出需求。
1096 12
|
弹性计算 安全 网络安全
阿里云ECS经典网络和专有网络有什么区别?
阿里云面向客户提供的网络类型服务有经典网络和专有网络两种,但这两者有什么区别呢?阿里官网给的解释是: 经典网络:IP地址由阿里云统一分配,配置简便,使用方便,适合对操作易用性要求比较高、需要快速使用 ECS 的用户。
|
人工智能 监控 算法
阿里云智慧高速解决方案 让高速公路出行更安全、更畅通、更舒心
首先,交通安全问题突出,交通事故频发;第二,智慧管控手段不足,大多依靠表格化的 OA 系统;第三,管理运营水平不高,主要流程依靠人为推动发现;第四,公众出行体验较差,交通拥堵事件频发。
4291 0
阿里云智慧高速解决方案 让高速公路出行更安全、更畅通、更舒心

热门文章

最新文章