当前位置: 首页 > java >正文

java设计模式一、单例模式

什么是单例模式

单例模式是一种常用的软件设计模式,它的核心思想是确保一个类只有一个实例,并且提供一个全局访问点来获取这个实例。简单来说,就是“只创建一个对象,或者每次使用的对象都是同一个对象”。

这种模式在许多场景下非常有用,比如配置信息管理、线程池、数据库连接池、日志对象等,这些情况下我们需要确保全局只有一个实例存在,以避免资源浪费或状态不一致的问题。

单例模式的构建原则

要实现一个正确的单例模式,需要遵循以下几个重要原则:

  1. 私有构造方法 - 防止类被通过常规的方法实例化,确保只能通过特定方式创建实例
  2. 以静态方法或枚举返回实例 - 提供统一的访问点,保证实例的唯一性
  3. 确保多线程环境下的安全 - 特别是在创建实例时需要保证线程安全
  4. 防止反序列化破坏单例 - 在序列化/反序列化场景下,确保不会重复构建对象

单例模式的创建方式

主动处理方式

  1. synchronized关键字 - 通过同步机制保证线程安全
  2. volatile关键字 - 保证变量的可见性和防止指令重排序
  3. CAS(Compare And Swap) - 通过原子操作实现无锁线程安全

JVM机制保障

  1. 静态初始化器 - 在静态字段或static{}块中的初始化器初始化数据时
  2. final字段访问 - 访问final字段时的特殊处理
  3. 线程创建前对象创建 - 在创建线程之前创建对象
  4. 对象可见性 - 线程可以看见它将要处理的对象

单例模式的分类与实现

下面是单例模式的主要分类示意图:

在这里插入图片描述

1. 静态常量方式(饿汉式)

public class Singleton_1 {public static ArrayList<String> list = new ArrayList<>();/*** 在JVM初始化的时候就会加载静态变量* 常用于保存全局配置或共享数据* 由YA33编写*/
}

特点

  • 类加载时就完成初始化,避免了线程同步问题
  • 没有达到懒加载的效果,如果从未使用过这个实例,会造成内存浪费

2. 饿汉模式

public class Singleton_2 {// 在类加载时就创建实例private static Singleton_2 instance = new Singleton_2();// 私有化构造方法,不对外提供private Singleton_2() {// 初始化代码}// 返回已经创建好的对象public static Singleton_2 getInstance() {return instance;}/*** 在应用启动的时候就创建对象* 类似于一个很饿的人,上来就要吃饭* 由YA33编写*/
}

优缺点分析

  • 优点:写法简单,类加载时就完成实例化,避免了线程同步问题
  • 缺点:没有达到懒加载的效果,如果从始至终从未使用过这个实例,则会造成内存的浪费

3. 懒汉模式(基础版)

public class Singleton_3 {private static Singleton_3 instance;private Singleton_3() {// 初始化代码}public static Singleton_3 getInstance() {if (instance == null) {instance = new Singleton_3();}return instance;}/*** 只有当对象不存在的时候才会去创建,存在就不创建了* 就像一个吃饱饭的人,只有饿了才会去备饭* 但是如果有多个人同时来创建的话可能会同时创建多个* 所以它是线程不安全的* 由YA33编写*/
}

线程安全问题说明
在多线程环境下,如果两个线程同时判断instance == null,可能会创建两个实例,违反了单例原则。

4. 双检锁懒汉模式(DCL)

public class Singleton_4 {// 使用volatile保证可见性和防止指令重排private static volatile Singleton_4 instance;private Singleton_4() {// 初始化代码}public static Singleton_4 getInstance() {if (instance == null) { // 第一次检查synchronized (Singleton_4.class) { // 加锁if (instance == null) { // 第二次检查instance = new Singleton_4();}}}return instance;}/*** 双检锁模式既实现了延迟加载,又保证了线程安全* 同时减少了同步代码块的使用,提高了效率* 由YA33编写*/
}

为什么要使用volatile?
对象的创建过程分为三步:

  1. 在堆中开辟空间
  2. 实例化各种参数
  3. 将对象的指针指向堆内存的空间

如果没有volatile,可能会发生指令重排,导致其他线程获取到未完全初始化的实例。

双检锁机制的执行流程如下:

在这里插入图片描述

5. 静态内部类方式

public class Singleton_5 {// 私有构造函数private Singleton_5() {// 初始化代码}// 静态内部类private static class SingletonHolder {private static final Singleton_5 INSTANCE = new Singleton_5();}// 获取实例public static Singleton_5 getInstance() {return SingletonHolder.INSTANCE;}/*** 不需要加锁,可以保证线程安全,实现了延迟加载* 由JVM保证线程安全性,在类进行初始化时,其他线程无法进入* 缺点是不能传递参数* 由YA33编写*/
}

工作原理
静态内部类不会在外部类加载时立即加载,而是在调用getInstance()方法时才会加载SingletonHolder类并初始化INSTANCE,这既实现了懒加载,又由JVM保证了线程安全。

6. CAS方式实现

import java.util.concurrent.atomic.AtomicReference;public class Singleton_6 {private static final AtomicReference<Singleton_6> INSTANCE = new AtomicReference<>();private Singleton_6() {// 初始化代码}public static Singleton_6 getInstance() {for (;;) {Singleton_6 current = INSTANCE.get();if (current != null) {return current;}current = new Singleton_6();if (INSTANCE.compareAndSet(null, current)) {return current;}}}/*** 使用CAS(Compare And Swap)原子操作实现无锁线程安全* 适合高并发场景,避免了锁的开销* 但在竞争激烈的情况下可能会多次创建实例(但最终只会有一个成功)* 由YA33编写*/
}

CAS原理说明
CAS是一种无锁算法,它包含三个操作数:内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,处理器会自动将该位置值更新为新值,否则不做操作。

7. 枚举方式

public enum Singleton_7 {INSTANCE;// 可以添加任意方法public void doSomething() {System.out.println("Hello World");}/*** 枚举方式实现单例是《Effective Java》作者Joshua Bloch推荐的方式* 它不仅能避免多线程同步问题,还能防止反序列化重新创建新的对象* 绝对防止多次实例化,是更简洁、安全的单例实现方式* 由YA33编写*/
}

枚举单例的优势

  1. 写法简单
  2. 线程安全有保障
  3. 防止反序列化破坏单例
  4. 防止反射攻击

如何选择单例实现方式

根据不同的场景,可以选择合适的单例实现方式:

实现方式线程安全懒加载性能防序列化破坏适用场景
饿汉式简单应用,实例小且一定会用到
懒汉式(同步)不推荐使用
双检锁需要懒加载且对性能有一定要求
静态内部类需要懒加载且实现简单
枚举最佳实践,推荐使用
CAS不定高并发场景,避免锁竞争

总结

单例模式是设计模式中最简单但也是最常被问到的模式之一。正确实现单例模式需要考虑线程安全、懒加载、序列化等问题。在实际开发中,推荐优先使用枚举方式实现单例,因为它简洁、安全且功能完整。如果需要有懒加载效果,静态内部类方式是一个不错的选择。在特别高并发的场景下,可以考虑使用CAS方式避免锁竞争。

无论选择哪种方式,都应该根据具体业务场景和需求来决定,确保在满足功能需求的同时,保证代码的性能和可维护性。

http://www.xdnf.cn/news/19577.html

相关文章:

  • 【K8s】整体认识K8s之Configmap、Secret/ResourceQuota资源配额/访问控制
  • Linux应用开发-windows,linux环境下相关工具
  • Adobe Illustrator 2025最新破解教程下载安装教程,Illustrator2025最新版下载
  • AI 安全与伦理:当大模型拥有 “决策能力”,我们该如何建立技术边界与监管框架?
  • 新手向:前端开发中的常见问题
  • NLP大语言模型数据准备
  • 基于 DNA 的原核生物与微小真核生物分类学:分子革命下的范式重构​
  • Shell编程(二):正则表达式
  • FastK v1.1 安装与使用-生信工具59
  • Gradle vs. Maven,Java 构建工具该用哪个?
  • 喜讯!华清远见参与制定的《电子产品印制电路板可制造性设计(DFM)和可靠性设计规范》正式发布
  • 【无标题】训练、推理适用的数据类型
  • 专题:2025全球新能源汽车供应链核心领域研究报告|附300+份报告PDF、数据仪表盘汇总下载
  • 关闭页面强制清除所有循环定时器
  • ES6手录02-字符串与函数的扩展
  • Kotlin 协程异步任务工具类:高效处理异步操作与超时控制
  • UE5 为啥原生的NotifyState写逻辑会有问题
  • 开源低代码平台(NocoBase)
  • 20250828的学习笔记
  • 9.1日IO作业
  • 2025年09月01日Github流行趋势
  • 99、23种设计模式之组合模式(8/23)
  • 09.《路由基础知识解析和实践》
  • 基于外部对照数据借用的临床试验统计分析方案设计与仿真研究
  • PitVis-2023挑战赛:内镜下垂体瘤手术视频中的手术流程识别|文献速递-深度学习人工智能医疗图像
  • 如何把指定阿里云文件夹下的所有文件移动到另一个文件夹下,移动文件时把文件名称(不包括文件后缀)进行md5编码
  • 从理论到实践,深入剖析数据库水平拆分的安全平滑落地
  • Spark自定义累加器实现高效WordCount
  • Spark和Spring整合处理离线数据
  • promptoMANIA-AI绘画提示词生成器