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

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.dtcms.com/a/361597.html

相关文章:

  • 查看LoRA 哪个适配器处于激活状态(67)
  • 【秋招笔试】2025.08.31小红书秋招笔试真题
  • 鸿蒙NEXT开发指南:Image、Video与Swiper组件全面解析
  • Mac idea 格式化代码快捷键
  • 用滑动窗口与线性回归将音频信号转换为“Token”序列:一种简单的音频特征编码方法
  • 若依vue自定义发布环境部署后所有菜单无法点击
  • Kubernetes一网络组件概述
  • 如何正确使用ChatGPT做数学建模比赛——数学建模AI使用技巧
  • Sqlsugar补充自定义模板
  • 环境搭建汇总
  • 在.NET标准库中进行数据验证的方法
  • 【qwen3vsglm4.5】JavaScript 与浏览器事件分类
  • 垃圾渗滤液中镍超标怎么处理
  • 亮数据MCP——专为信息爆炸时代打造的AI新闻利器。
  • 如何选择最佳车载交换机?车载交换机功能讲解
  • UCIE Specification详解(十二)
  • 【小白入】显示器核心参数对比度简介
  • Trae + MCP : 一键生成专业封面
  • (论文速读)3DTopia-XL:高质量3D资产生成技术
  • C语言:树的实现和剖析
  • 火狐退出中国后,Zen 浏览器会是「理想平替」吗?
  • MATLAB实现图像分割:Otsu阈值法
  • 辅助日志/备份文件自动化命名方案
  • 展会回顾 | 聚焦医疗前沿 , 礼达先导在广州医博会展示类器官自动化培养技术
  • 解析简历重难点与面试回答要点
  • Redis基础教程
  • 构建线上门户的核心三要素:域名、DNS与IP 全面解析
  • 移动开发如何给不同手机屏幕做适配
  • RK3588部署yolov8目标检测
  • Rust序列化与反序列化-Serde 开发指南:从入门到实战