设计模式笔记_创建型_单例模式

1.单例模式介绍

1.1 简介

单例(Singleton)模式是一种创建型设计模式,旨在确保一个类只有一个实例,并提供一个全局访问点来获取该实例。 这在需要对某个资源进行集中管理或限制其实例化时非常有用,比如日志记录器、配置管理器或者连接池。

可以将Singleton模式类比为一个总统职位。 在一个国家里,通常只能有一个总统,这个职位的独特性意味着不管你在哪里提到总统,都指的是同一个人。 这个职位的管理需要集中化,这样才能确保权力和决策的一致性。

1.1 实现要点:

  • 私有构造函数:阻止外部类直接实例化。
  • 静态实例:在类内部创建一个静态实例。
  • 公共静态方法:提供一个公共的静态方法来获取唯一的实例。

1.2 使用演示

step1. 新建单例类:

java 复制代码
/**
 * 实现要点
 * - 私有构造函数:阻止外部类直接实例化。
 * - 静态实例:在类内部创建一个静态实例。
 * - 公共静态方法:提供一个公共的静态方法来获取唯一的实例。
 */
public class Singleton {
    /**
     * 私有静态实例,确保唯一性
     */
    private static Singleton instance;

    /**
     * 私有构造函数,防止外部实例化
     */
    private Singleton() {}

    /**
     * 公共静态方法,提供全局访问点
     *
     * @return Singleton
     */
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }

    /**
     * 示例方法
     */
    public void showMessage() {
        System.out.println("Hello, I am a Singleton!");
    }

}

step2. 调用单例方法

java 复制代码
public class SingletonDemo {
    public static void main(String[] args) {
        // 获取Singleton实例
        Singleton singleton = Singleton.getInstance();

        // 调用实例方法
        singleton.showMessage();
    }
}

2.单例实现方式

2.1 饿汉式

代码:

java 复制代码
public class HungryStyleIdGenerator {
    /**
     * 在类加载的时候,instance静态实例就已经创建并初始化好了
     */
    private static final HungryStyleIdGenerator INSTANCE = new HungryStyleIdGenerator();
    private final AtomicLong atomicLong = new AtomicLong(0);

    private HungryStyleIdGenerator() {}

    public static HungryStyleIdGenerator getInstance() {
        return INSTANCE;
    }

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

优点

  • 实现最简单
  • 线程绝对安全
  • 没有并发性能损耗

缺点

  • 非延迟加载(类加载时立即初始化)
  • 可能造成资源浪费

适用场景

  • 实例较小且使用频繁
  • 对内存敏感的系统中慎用

2.2 懒汉式

代码:

java 复制代码
public class LazyStyleIdGenerator {
    private static LazyStyleIdGenerator INSTANCE;

    private final AtomicLong atomicLong = new AtomicLong(0);

    private LazyStyleIdGenerator() {}

    /**
     * 方法加了锁,导致这个函数的并发度很低(并发度为1)
     */
    public static synchronized LazyStyleIdGenerator getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new LazyStyleIdGenerator();
        }
        return INSTANCE;
    }

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

优点

  • 实现简单
  • 延迟加载(按需初始化)
  • 线程安全

缺点

  • 并发度低
  • 代码相对复杂

适用场景

  • 实例创建成本高且使用频率不确定时
  • 需要严格控制资源分配的场景

2.3 双重检测

代码:

java 复制代码
public class DoubleCheckStyleIdGenerator {
    private static DoubleCheckStyleIdGenerator INSTANCE;

    private final AtomicLong atomicLong = new AtomicLong(0);

    private DoubleCheckStyleIdGenerator() {}

    /**
     * 只要instance被创建之后,即便再调用getInstance()函数也不会再进入到加锁逻辑中了。所以,这种实现方式解决了懒汉式并发度低的问题。
     */
    public static DoubleCheckStyleIdGenerator getInstance() {
        if (INSTANCE == null) {
            synchronized (DoubleCheckStyleIdGenerator.class) {
                if (INSTANCE == null) {
                    INSTANCE = new DoubleCheckStyleIdGenerator();
                }
            }
        }
        return INSTANCE;
    }

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

优点

  • 延迟加载
  • 高并发性能好
  • 内存占用优化

缺点

  • 实现复杂
  • 可读性较差

适用场景

  • 高并发系统中需要延迟加载
  • 对性能要求极高的场景

2.4 静态内部类

代码:

java 复制代码
/**
 * instance的唯一性、创建过程的线程安全性,都由JVM来保证。
 *
 * Java的类加载机制是线程安全的,具体由JVM保证:
 * - 当多个线程同时尝试访问一个未加载的类时,JVM会确保只由一个线程触发类的初始化,其他线程会等待。
 * - 类的初始化(包括静态代码块和静态变量赋值)是同步的,通过Class对象的锁(monitor)实现。
*/
public class StaticInnerClassStyleIdGenerator {
    private final AtomicLong atomicLong = new AtomicLong(0);

    private StaticInnerClassStyleIdGenerator() {}

    /**
     * 静态内部类,当外部类 StaticInnerClassStyleIdGenerator 被加载的时候,并不会创建SingletonHolder实例对象。
     * 只有当调用getInstance()方法时,SingletonHolder才会被加载,这个时候才会创建instance。
     */
    private static class SingletonHolder {
        private static final StaticInnerClassStyleIdGenerator INSTANCE = new StaticInnerClassStyleIdGenerator();
    }

    public static StaticInnerClassStyleIdGenerator getInstance() {
        return SingletonHolder.INSTANCE;
    }

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

优点

  • 实现简单优雅
  • 延迟加载
  • 线程安全
  • 不需要额外同步代码

缺点

  • JVM类加载机制依赖

适用场景

  • 推荐作为默认实现方式
  • 适用于大多数单例需求场景

2.5 枚举

代码:

java 复制代码
public enum EnumStyleIdGenerator {
    /**
     * 通过Java枚举类型本身的特性,保证了实例创建的线程安全性和实例的唯一性
     */
    INSTANCE;

    private final AtomicLong atomicLong = new AtomicLong(0);

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

优点

  • 简洁优雅
  • 天然线程安全
  • 防止反序列化破坏
  • 防止反射攻击
  • 支持序列化

缺点

  • 不支持延迟初始化
  • 不符合传统单例模式结构

适用场景

  • 需要绝对线程安全的场景
  • 需要序列化的单例对象
  • 项目规范允许使用枚举时

2.6 单例的5种实现方式对比

实现方式 延迟加载 线程安全 反射破坏 序列化安全 性能 推荐度
饿汉式 最高 ★★★☆
懒汉式 中等 ★★☆
双重检查(DCL) ★★★☆
静态内部类 ★★★★☆
枚举 ★★★★

使用建议:

  1. 优先选择:静态内部类实现(兼顾延迟加载和线程安全)
  2. 绝对安全:枚举方式(推荐用于金融、安全等关键系统)
  3. 特殊需求
    • 需要延迟初始化 + 高并发 → 双重检测DCL
    • 实例小且高频使用 → 饿汉式
    • 传统项目 → 懒汉式(注意同步)
相关推荐
sz-lcw17 分钟前
MySQL知识笔记
笔记·mysql·adb
古译汉书1 小时前
嵌入式铁头山羊STM32-各章节详细笔记-查阅传送门
数据结构·笔记·stm32·单片机·嵌入式硬件·个人开发
callJJ1 小时前
从 0 开始理解 Spring 的核心思想 —— IoC 和 DI(2)
java·开发语言·后端·spring·ioc·di
wangjialelele1 小时前
Linux中的线程
java·linux·jvm·c++
谷咕咕1 小时前
windows下python3,LLaMA-Factory部署以及微调大模型,ollama运行对话,开放api,java,springboot项目调用
java·windows·语言模型·llama
没有bug.的程序员2 小时前
MVCC(多版本并发控制):InnoDB 高并发的核心技术
java·大数据·数据库·mysql·mvcc
在下村刘湘2 小时前
maven pom文件中<dependencyManagement><dependencies><dependency> 三者的区别
java·maven
不务专业的程序员--阿飞3 小时前
JVM无法分配内存
java·jvm·spring boot
李昊哲小课3 小时前
Maven 完整教程
java·maven
2301_800050993 小时前
DNS 服务器
linux·运维·笔记