设计模式——单例模式详解
单例
介绍
单例模式(Singleton Pattern)是Java中最基础最简单的设计模式之一,这种模式属于创建型模式
,提供的就是一种创建对象的方式。这种模式中的单一类创建自己的对象,确保只有一个对象被创建,并且为所有对象提供一个访问它的全局访问点。
单例模式用来解决频繁创建与销毁实例对象问题,当我们想要控制实例创建个数或者复用实例时,就可以使用单例模式,这样有助于节省系统资源。
特点
- 单例类,顾名思义,只有一个实例。
- 单例类必须是自己创建自己的唯一实例。
- 单例类必须给所有对象提供这个唯一实例。
- 构造函数私有。
应用场景
适用范围
- 频繁的访问数据库或文件的对象。
- 频繁的需要实例化,然后销毁的对象。
- 创建对象耗时长且耗资源,但是又需要常用的对象。
使用举例
- Windows的任务管理器,每次只能打开一个。
- 应用程序的日志应用。
- 网上在线人数统计。
- 配置文件的访问类。
- 数据库的连接池。
- 多线程的线程池。
- 操作系统的文件系统。
设计思路
- 一个类每次都返回唯一的对象(每次返回都是同一个对象实例)。
- 提供一个获取该实例的方法。(基本上都是静态方法)
- 构造函数私有。
加载分类
提前加载
:在应用开始时就创建单例实例。延迟加载
:在getInstance()方法
首次被调用时才调用单例创建,即需要使用时,才加载创建单例对象实例。
单例分类
1 饿汉式单例
介绍
- 类加载时就初始化好实例对象,容易产生垃圾对象,浪费内存。
- 无锁,执行效率高。
- 天生线程安全,因为类加载时就已初始化好实例。
代码
/**
* 饿汉式单例
* @author Andya
* @date 2021/3/9
*/
public class EHanSingleInstance {
//饿汉式:初始化时就建好实例
private static final EHanSingleInstance instance = new EHanSingleInstance();
private EHanSingleInstance() {
doSomething();
}
public static EHanSingleInstance getInstance() {
return instance;
}
public void doSomething() {
}
}
2 懒汉式单例
介绍
- 饿汉式属于延迟加载初始化。
- 若不加锁,则为非线程安全的。
- 若加锁,则为线程安全的,但效率很低,基本上每次都要同步。
无锁懒汉式代码
/**
* 无锁懒汉式单例,非线程安全
* @author Andya
* @date 2021/3/9
*/
public class LanHanNoLockSingleInstance {
//懒汉式:初始化时不创建实例,等需要时再创建
private static LanHanNoLockSingleInstance instance;
private LanHanNoLockSingleInstance() {
doSomething();
}
public static LanHanNoLockSingleInstance getInstance() {
//先判断是否为空,若为空创建一个新的实例
if (instance == null) {
instance = new LanHanNoLockSingleInstance();
}
return instance;
}
public void doSomething() {
}
}
加锁懒汉式代码
/**
* 加锁懒汉式单例,线程安全
* @author Andya
* @date 2021/3/9
*/
public class LanHanWithLockSingleInstance {
//懒汉式:初始化时不创建实例,等需要时再创建
private static LanHanWithLockSingleInstance instance;
private LanHanWithLockSingleInstance() {
doSomething();
}
public static synchronized LanHanWithLockSingleInstance getInstance() {
//先判断是否为空,若为空创建一个新的实例
if (instance == null) {
instance = new LanHanWithLockSingleInstance();
}
return instance;
}
public void doSomething() {
}
}
3 双重检锁式单例
介绍
在多线程应用中使用这种模式可以保证线程安全。因为如果实例为空,有可能存在两个线程同时调用getInstance()
方法的情况,这样的话,第一个线程会首先使用新构造器实例化一个单例对象,但此时它还没有完成单例对象的实例化操作,同时第二个线程也检查到单例实例为空,也会开始实例化单例对象,这就造成了2次实例化对象。所以多线程应用中,需要锁
来检查实例是否线程安全。
- 静态方法锁
public static synchronized Singleton getInstance()
- 代码块锁
synchronized(SingleInstance.class){
if (instance == null) {
instance = new SingleInstance();
}
}
虽然加锁可以保证线程安全,但是会带来延迟,因为加锁后,代码块在同一时刻只能被一个线程执行,但是同步锁只有在实例没被创建的时候才会起作用。如果单例实例已经被创建,其实不需要走该步骤。因此,我们可以在代码块锁外面再加一层实例空判断。instance == null
被检查2
次。
if (instance == null) {
synchronized(SingleInstance.class){
if (instance == null) {
instance = new SingleInstance();
}
}
}
代码
/**
* 双检锁单例
* @author Andya
* @date 2021/3/9
*/
public class DoubleCheckSingleInstance {
//使用volatile保证多线程的可见性
private static volatile DoubleCheckSingleInstance instance;
private DoubleCheckSingleInstance() {
doSomething();
}
public static DoubleCheckSingleInstance getInstance() {
//第一次检查是否创建过该单例
if (instance == null) {
//加锁,保证线程安全
synchronized (DoubleCheckSingleInstance.class) {
if (instance == null) {
instance = new DoubleCheckSingleInstance();
}
}
}
return instance;
}
public void doSomething() {
}
}
4 静态内部类单例
介绍
- 延迟加载初始化实例instance。
- 线程安全。
- 该方式只适用于静态域的情况。
- 该方式单例不会立即初始化,只有显式调用getInstance()方法时,才会显式转载静态内部类,才会实例化instance。
代码
/**
* 静态内部类单例
* @author Andya
* @date 2021/3/9
*/
public class StaticInternalSingleInstance {
//静态内部类
private static class StaticInternalSingleInstanceHolder{
private static final StaticInternalSingleInstance INSTANCE
= new StaticInternalSingleInstance();
}
private StaticInternalSingleInstance() {
doSomething();
}
public static final StaticInternalSingleInstance getInstance() {
return StaticInternalSingleInstanceHolder.INSTANCE;
}
public void doSomething() {
}
}
5 枚举类单例
介绍
- 不属于延迟加载初始化实例instance。
- 多线程安全。
- 支持序列化机制,从而防止多次实例化。
代码
/**
* 枚举型单例
* @author Andya
* @date 2021/3/9
*/
public enum EnumSingleInstance {
INSTANCE;
public EnumSingleInstance getInstance(){
return INSTANCE;
}
}
总结
一般在开发应用中,建议使用第1种饿汉式单例,而不推荐使用第2种懒汉式单例,除非明确需要延迟加载时,才会使用第4种静态内部类单例,若涉及到反序列化创建对象时,推荐使用第5种枚举式单例。其他也可以考虑使用第3种双重检锁式单例。
烧不死的鸟就是凤凰
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET10 - 预览版1新功能体验(一)
2020-03-12 Java—Remove Deplicates from Sorted Array(顺序数组中去重位置)
2020-03-12 Git—代码管理、提交及冲突解决流程的思考