「全网最细 + 实战源码案例」设计模式——单例设计模式

embedded/2025/1/24 2:08:13/

核心思想:

  • 属于创建型设计模式,核心目的是确保一个类在整个程序运行期间只有一个实例,并提供一个全局访问点来获取该实例。

  • 控制共享资源的访问(如数据库链接、配置管理、日志处理器等)

  • 真实世界类比:政府是单例模式的一个很好的示例。 一个国家只有一个官方政府。 不管组成政府的每个人的身份是什么,“某政府” 这一称谓总是鉴别那些掌权者的全局访问节点。


结构

所有单例的实现都包含以下两个相同的步骤:

  • 将默认构造函数设为私有,防止其他对象使用单例类的 new 运算符。

  • 新建一个静态构建方法作为构造函数。该函数会“偷偷”调用私有构造函数来创建对象,并将其保存在一个静态成员变量中。此后所有对于该函数的调用都将返回这一缓存对象。

如果你的代码能够访问单例类,那它就能调用单例类的静态方法。无论何时调用该方法,它总是会返回相同的对象。


使用场景:

1. 需要唯一实例的场景:

  • 配置管理类

  • 日志记录器

  • 数据库连接池

  • 多线程环境中的任务调度器

2. 需要全局共享实例

  • 以避免多个实例引发资源冲突或影响程序逻辑。


⭐实现方式:

1. 饿汉式(线程安全,类加载时初始化)

1.1. 静态变量式(常见方式)

// 饿汉式(静态变量)
public class Singleton {// 1. 私有化构造方法private Singleton() {}// 2. 创建一个静态变量,保存实例private static final Singleton instance = new Singleton();// 3. 提供一个公共的静态方法获取实例public static Singleton getInstance() {return instance;}
}

特点:

  • 线程安全:类加载时实例化,JVM 保证线程安全。

  • 缺点:类加载时即创建实例,即使未使用也会占用内存。


1.2. 静态代码块式

// 饿汉式(静态代码块)
public class Singleton {// 1. 私有化构造方法private Singleton(){}// 2. 创建一个静态对象private static Singleton instance;// 3. 在静态代码块中创建对象static {instance = new Singleton();}// 4. 提供获取对象的方法public static Singleton getInstance(){return instance;}
}

特点:

  • 和静态变量方式类似,在类加载时实例化。

  • 可以在静态代码块中加入额外逻辑,例如异常处理或配置初始化。


2. 懒汉式(线程不安全,延迟加载)

// 懒汉式,线程不安全
public class Singleton {// 1. 私有化构造方法private Singleton() {}// 2. 定义一个静态变量,用于存储唯一实例private static Singleton instance;// 3. 定义一个静态方法,用于获取唯一实例public static Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}
}

优点:

  • 实例在第一次使用时才初始化,节约资源。

缺点:

  • 多线程情况下可能创建多个实例,线程不安全。


3. 线程安全的懒汉式

3.1. 同步方法

// 懒汉式,同步式,线程安全
public class Singleton {// 1. 私有化构造方法private Singleton() {}// 2. 定义一个静态变量,用于存储private static Singleton instance;// 3. 定义一个静态方法,用于获取唯一实例public static synchronized Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}
}

缺点:

  • 同步方法会导致性能下降,尤其是高并发访问时。

3.2. 双重检查锁(推荐)

// 懒汉式,双重检查锁方式
public class Singleton {// 1. 私有化构造方法private Singleton() {}// 2. 定义一个静态变量,用于存储实例,volatile保证可见性与有序性,避免指令重排private static volatile Singleton instance;// 3. 定义一个静态方法,用于获取唯一实例public static Singleton getInstance() {// 1.第一次判断,如果instance的值为null,则进入同步代码块if (instance == null) {// 2.同步代码块,保证线程安全synchronized (Singleton.class) {// 3.第二次判断,如果instance的值为null,则创建实例if (instance == null) {instance = new Singleton();}}}return instance;}
}

优点:

  • 高效,只有在首次实例化时会加锁,之后不会。

注意:volatile 关键字防止指令重排,确保线程安全。

⭐为什么必须要加 volatile

1、防止指令重排

在 Java 中,对象的实例化过程分为三步:

  • 分配内存空间

  • 初始化对象

  • 将内存地址赋值给变量

由于指令重排的存在,步骤 2 和步骤 3 可能被调换执行。例如:

线程 A 在执行 instance = new Singleton() 时,可能执行了分配内存和赋值操作,但还未完成初始化。

此时,instance 已经不为 null,但它指向的对象尚未完全初始化。

如果线程 B 此时调用 getInstance(),判断 instance != null 为真,但实际访问的是一个未初始化完全的对象,这将导致程序出错。

加上 volatile 后,禁止指令重排序,确保初始化顺序正确。

2、保证变量的可见性

Java 的内存模型中,每个线程有自己的工作内存。一个线程对变量的修改,可能不会立即被其他线程所见。

加上 volatile 后,保证每次对 instance 的读操作都能获取到最新的值

当线程 A 完成 instance 初始化后,其他线程(如 B 线程)立刻可见,而不会读取到旧值或中间状态。


3.3. ⭐静态内部类(推荐)

// 懒汉式,静态内部类方式
public class Singleton {// 1.构造函数私有化,外部不能newprivate Singleton() {}// 2.创建静态内部类private static class SingletonHolder {// 3.创建静态变量,保存实例private static final Singleton INSTANCE = new Singleton();}// 3.定义一个静态方法,用于获取唯一实例public static Singleton getInstance() {return SingletonHolder.INSTANCE;}
}

原理:

  • 由于 JVM 加载外部类的过程中,不会加载静态内部类,只有内部类的属性/方法被调用时才会被加载,并初始化其静态属性。静态属性由于被 static 修饰,保证只会被实例化一次,并且严格保证实例化顺序。

优点:

  • 线程安全

  • 实现了延迟加载,按需初始化


4. ⭐枚举单例(最安全,推荐)

// 枚举单例
public enum Singleton {INSTANCE;
}

优点:

  • 简单

  • 天然防止反射和序列化破坏单例


破坏单例

1. 序列化破坏单例

问题:序列化和反序列化可以通过 ObjectInputStream 创建一个新的实例,而不是返回现有的单例实例。

示例代码:

import java.io.*;public class Singleton implements Serializable {private static final long serialVersionUID = 1L;private static final Singleton INSTANCE = new Singleton();private Singleton() {}public static Singleton getInstance() {return INSTANCE;}public static void main(String[] args) throws Exception {Singleton instance1 = Singleton.getInstance();// 将对象序列化到文件ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton.obj"));oos.writeObject(instance1);oos.close();// 从文件反序列化对象ObjectInputStream ois = new ObjectInputStream(new FileInputStream("singleton.obj"));Singleton instance2 = (Singleton) ois.readObject();// 验证是否为同一个实例System.out.println(instance1 == instance2); // 输出:false}
}

原因

  • 序列化机制会通过反序列化的过程创建一个新的对象实例,而不会调用单例类中的 getInstance() 方法。

解决方案:实现 readResolve() 方法,确保反序列化时返回现有实例。

private Object readResolve() {return INSTANCE;
}

2. 反射破坏单例

问题:通过反射,能够直接调用私有构造方法,创建多个实例。

示例代码:

import java.lang.reflect.Constructor;public class Singleton {private static final Singleton INSTANCE = new Singleton();private Singleton() {}public static Singleton getInstance() {return INSTANCE;}public static void main(String[] args) throws Exception {Singleton instance1 = Singleton.getInstance();// 使用反射创建新实例Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();constructor.setAccessible(true);Singleton instance2 = constructor.newInstance();// 验证是否为同一个实例System.out.println(instance1 == instance2); // 输出:false}
}

原因

  • 反射可以访问私有构造方法并直接调用,从而绕过单例模式的限制。

解决方案

1、在构造方法中防止重复实例化

private static boolean isCreated = false;private Singleton() {if (isCreated) {throw new RuntimeException("Singleton instance already created!");}isCreated = true;
}

2、使用枚举单例

枚举类的单例天然防止反射和序列化破坏。

public enum Singleton {INSTANCE;
}

3. 总结

  • 序列化破坏:通过 readResolve() 方法解决。

  • 反射破坏:通过构造方法检查或使用枚举单例解决。

  • 推荐方式:使用 枚举单例,最简单且最安全,能有效防止这两种破坏。


在源码中的应用

1. Runtime 类

  • 简介Runtime 类允许应用程序与运行时环境交互,比如调用垃圾回收、运行外部命令等。

  • 实现方式:通过 饿汉式单例 实现。

源码分析:

public class Runtime {private static final Runtime currentRuntime = new Runtime(); // 饿汉式实例化private Runtime() {} // 私有化构造方法public static Runtime getRuntime() {return currentRuntime; // 返回唯一实例}public void gc() {// 调用垃圾回收}public void exit(int status) {// 退出 JVM}
}

特点:
  • 全局唯一实例。

  • 使用饿汉式,保证线程安全。


2. Desktop 类

  • 简介Desktop 类用来打开用户默认的应用程序(如浏览器、邮件客户端等)。

  • 实现方式:通过 懒汉式单例 实现。

源码分析:

public final class Desktop {private static Desktop desktop;private Desktop() {}public static synchronized Desktop getDesktop() {if (desktop == null) {desktop = new Desktop(); // 懒汉式单例}return desktop;}public void browse(URI uri) {// 打开 URI}
}

特点:
  • 使用同步方法保证线程安全。

  • 懒加载,实例在需要时创建。


3. Logger 类( java.util.logging.Logger )

  • 简介Logger 是 Java 的日志工具类,用于记录和管理应用程序日志。

  • 实现方式:内部使用单例模式管理全局日志管理器(LogManager)。

源码分析(核心部分):

public class Logger {private static final LogManager manager = LogManager.getLogManager(); // 单例的 LogManagerprotected Logger(String name, String resourceBundleName) {// Logger 构造方法}public static Logger getLogger(String name) {return manager.getLogger(name); // 通过单例 LogManager 获取 Logger}
}

特点:

  • LogManager 作为单例管理所有 Logger 实例。

  • getLogger 方法确保每个名称对应的 Logger 是唯一的。


4. 总结

在 JDK 源码中,单例模式被广泛应用于需要 全局唯一实例 或 资源共享 的场景:

  1. 饿汉式Runtime 类。

  2. 懒汉式Desktop 类。

  3. 组合模式Logger 类中的 LogManager 单例。

这些设计的核心目标是:确保全局状态的一致性、节省资源以及简化管理

单例模式优缺点:

与其他模式的关系:

  1. 外观模式类通常可以转换为单例模式类, 因为在大部分情况下一个外观对象就足够了。

  2. 如果你能将对象的所有共享状态简化为一个享元对象, 那么享元模式就和单例类似了。 但这两个模式有两个根本性的不同。

  3. 只会有一个单例实体, 但是享元类可以有多个实体, 各实体的内在状态也可以不同。

    单例对象可以是可变的。 享元对象是不可变的。

  4. 抽象工厂模式、 生成器模式原型模式都可以用单例来实现。

文章转载自:SlackClimb

原文链接:「全网最细 + 实战源码案例」设计模式——单例设计模式 - SlackClimb - 博客园

体验地址:引迈 - JNPF快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构


http://www.ppmy.cn/embedded/156451.html

相关文章

Java学习,List截取

Java中List接口&#xff0c;List接口提供了一个subList方法&#xff0c;该方法允许获取原始列表的一个视图&#xff08;view&#xff09;&#xff0c;该视图包含从指定起始索引&#xff08;包含&#xff09;到指定结束索引&#xff08;不包含&#xff09;之间的元素。 使用Arr…

Spring Boot中选择性加载Bean的几种方式

说明&#xff1a;用过Spring框架的都知道其自动装配的特性&#xff0c;本文介绍几种选择性加载Bean的方式。Spring自动装配参考以下两篇文章&#xff1a; 基于SpringBoot的三层架构开发&统一响应结果 SpringBoot自动装配原理简单分析 ConditionalOnProperty Conditiona…

【Linux】APT 密钥管理迁移指南:有效解决 apt-key 弃用警告

引言 随着 Debian 11 和 Ubuntu 22.04 版本的推出&#xff0c;APT 的密钥管理方式发生了重大的变化。apt-key 命令被正式弃用&#xff0c;新的密钥管理机制要求使用 /etc/apt/keyrings/ 或 /etc/apt/trusted.gpg.d/ 来存储和管理密钥。这一变化对管理员和普通用户来说至关重要…

【Python】笔试面试题之生成器、闭包、字典

文章目录 一、生成器是什么1. 最简单的生成器2. 在函数中使用yield关键字 二、闭包是什么&#xff1f;怎么理解&#xff1f;1. 案例12. 案例2&#xff1a;实现项目的日志处理3. 案例3&#xff1a;装饰器就是一种闭包4. 总结 三、Python中字典的底层是怎么实现的1. 相关概念2. P…

Python从0到100(八十五):神经网络-使用迁移学习完成猫狗分类

前言&#xff1a; 零基础学Python&#xff1a;Python从0到100最新最全教程。 想做这件事情很久了&#xff0c;这次我更新了自己所写过的所有博客&#xff0c;汇集成了Python从0到100&#xff0c;共一百节课&#xff0c;帮助大家一个月时间里从零基础到学习Python基础语法、Pyth…

1. 基于图像的三维重建

1. 基于图像的三维重建 核心概念三维重建中深度图、点云的区别&#xff1f;深度图点云总结 深度图到点云还需要什么步骤&#xff1f;1. **获取相机内参**2. **生成相应的像素坐标**3. **计算三维坐标**4. **构建点云**5. **处理颜色信息&#xff08;可选&#xff09;**6. **去除…

centos哪个版本建站好?centos最稳定好用的版本

在信息化飞速发展的今天&#xff0c;服务器操作系统作为构建网络架构的基石&#xff0c;其稳定性和易用性成为企业和个人用户关注的重点。CentOS作为一款广受欢迎的开源服务器操作系统&#xff0c;凭借其强大的性能、出色的稳定性和丰富的软件包资源&#xff0c;成为众多用户建…

Windows7搭建Hadoop-2.7.3源码阅读环境问题解决列表

个人博客地址:Window7搭建Hadoop-2.7.3源码阅读环境问题解决列表 | 一张假钞的真实世界 环境说明 Windows 7java version “1.7.0_80”Apache Maven 3.2.3ProtocolBuffer 2.5.0cmake version 3.7.2 win64 x64Windows SDK 7.1构建过程参照源代码目录下BUILDING.txt说明文件中的…