【创建型设计模式】单例模式

news/2024/11/25 23:56:46/

【创建型设计模式单例模式

这篇博客接下来几篇都将阐述设计模式相关内容。

接下来的顺序大概是:单例模式、工厂方法模式、抽象工厂模式、建造者模式、原型模式。

一、什么是单例模式

单例模式是一种创建型设计模式,它保证一个类仅有一个实例,并提供一个全局访问点。

核心思想:

  1. 限制类的实例化次数,确保全局只有一个实例。
  2. 提供统一访问该实例的方法。

Client 为客户端,Singleton 是单例类,通过调用 Singleton.getInstance() 来获取实例对象。

二、单例模式的 6 种方法

(1) 饿汉模式
public class Singleton {private static Singleton instance = new Singleton();private Singleton() {}public static Singleton getInstance() {return instance;}
}

特点:

  • 初始化时间: 类加载时即完成实例化。
  • 访问效率: 调用 getInstance 时无需等待,性能高。

优点:

  • 基于类加载机制,天然线程安全。
  • 实现简单,适用于单例对象较少被频繁初始化的场景。

缺点:

  • 无法延迟加载,如果实例从未被使用,会浪费内存。
(2) 懒汉模式(线程不安全)
public class Singleton {private static Singleton instance;private Singleton() {}public static Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}
}

特点:

  • 初始化时间: 第一次调用时才实例化。
  • 访问效率: 多线程环境下存在问题,可能生成多个实例。

优点: 延迟加载,节省资源。

缺点: 线程不安全,多个线程同时调用可能创建多个实例,导致单例失效。

(3) 懒汉模式(线程安全)
public class Singleton {private static Singleton instance;private Singleton() {}public static synchronized Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}
}

特点:

  • 线程安全性: 使用 synchronized 关键字确保多线程下只生成一个实例。

优点: 在多线程环境中保证安全。

缺点: 同步锁导致性能下降,每次调用 getInstance 都需排队,影响效率。

(4) 双重检查模式(DCL)
public class Singleton {// 1. 声明 volatile 修饰的静态实例变量private static volatile Singleton instance;// 2. 私有化构造方法,防止外部直接实例化private Singleton() {}// 3. 提供对外获取实例的静态方法public static Singleton getInstance() {// 第一次检查:避免不必要的同步if (instance == null) {synchronized (Singleton.class) { // 加锁// 第二次检查:确保实例未被其他线程创建if (instance == null) {instance = new Singleton();}}}return instance;}
}

特点:

  • 性能优化: 减少同步开销。
  • 线程安全: 使用 volatile 避免指令重排序,确保多线程环境下正确创建实例。

优点: 资源利用率高,延迟初始化,线程安全,性能优于懒汉模式(线程安全版)。

缺点: 实现复杂,某些情况下可能出现 DCL 失效问题(如旧版 JVM)。

DCL 模式的核心思想

DCL 的核心是两次检查同步锁的结合。

**第一次检查(if (instance == null)):**在大多数情况下,实例已经被初始化,可以直接返回,不需要加锁。减少了不必要的同步,提高性能。

**加锁(synchronized):**在实例尚未初始化时,进入临界区,防止多个线程同时创建实例。

**第二次检查(if (instance == null)):**加锁后,再次检查实例是否为 null,防止多个线程同时通过第一次检查,确保单例对象只被创建一次。

**volatile 关键字:**确保 instance 的修改对所有线程立即可见,防止指令重排序导致未完全初始化的对象被其他线程访问。

为什么需要 volatile

问题:指令重排序

在没有 volatile 修饰时,JVM 编译器和 CPU 可能对以下代码进行优化:

instance = new Singleton();

这一行代码可能被分解为以下三步:

  1. 为对象分配内存。
  2. 初始化对象。
  3. 将对象引用赋值给 instance

但实际执行中,可能发生指令重排序

  1. 为对象分配内存。
  2. 将对象引用赋值给 instance
  3. 初始化对象。

如果线程 A 在步骤 2 后被切换,线程 B 进入并访问了 instance,此时会得到一个未初始化的对象,导致程序行为不可预期。

解决方法:volatile 关键字禁止指令重排序,确保对象初始化完成后才将引用赋值给 instance

(5) 静态内部类单例模式
public class Singleton {// 私有化构造方法,防止外部实例化private Singleton() {}// 静态内部类,持有单例实例private static class SingletonHolder {private static final Singleton INSTANCE = new Singleton();}// 提供全局访问点,返回静态内部类中的单例实例public static Singleton getInstance() {return SingletonHolder.INSTANCE;}
}

特点:

  • 延迟加载: 第一次调用 getInstance 时才加载内部类,完成实例化。
  • 线程安全: 内部类加载机制天然线程安全。

优点:

  • 实现简单,避免同步开销。
  • 兼具延迟加载和线程安全,推荐使用。

缺点: 无法控制实例销毁,适用单例生命周期较长的场景。

静态内部类的加载时机:

在 Java 中,类的加载是延迟的,只有在被真正使用时才会加载静态内部类。

主类被加载时,静态内部类不会被加载。

只有在调用静态内部类中的成员时,静态内部类才会被加载。

类加载的线程安全性:

JVM 在加载类时会自动保证线程安全。

静态变量 INSTANCE 只会被初始化一次。

(6) 枚举单例
public enum Singleton {INSTANCE; // 枚举单例的唯一实例// 单例类的其他方法public void doSomething() {System.out.println("Singleton instance is working!");}
}

特点:

  • 线程安全: 枚举的线程安全由 JVM 保证。
  • 防反序列化破坏: 默认枚举实例不可反序列化。

优点:

  • 实现简单,天生线程安全。
  • 避免反序列化和反射破坏单例。

缺点:

  • 不支持延迟加载。
  • 某些场景下(如需要灵活实例化控制)不适用。
public class Test {public static void main(String[] args) {Singleton instance = Singleton.INSTANCE;instance.doSomething();}
}

为什么枚举单例是线程安全的?

**JVM 保证:**枚举类型在类加载时会由 JVM 初始化,类加载过程是线程安全的。

**枚举的单一实例特性:**枚举中的每个实例在加载时被自动创建,并且不可被外部修改。

三、单例实现对比

实现方式是否线程安全是否延迟加载性能复杂度
饿汉模式简单
懒汉模式(不安全)简单
懒汉模式(同步)低(频繁加锁)简单
双重检查模式(DCL)较高中等
静态内部类模式简单
枚举单例简单

http://www.ppmy.cn/news/1549944.html

相关文章

Linux---ps命令

​​​​​​Linux ps 命令 | 菜鸟教程 (runoob.com) process status 用于显示进程的状态 USER: 用户名,运行此进程的用户名。PID: 进程ID(Process ID),每个进程的唯一标识号%CPU: 进程当前使用的CPU百分比%MEM: 进程当前使用的…

【网络云计算】2024第47周-每日【2024/11/21】周考-实操题-RAID6实操解析3

文章目录 一、标记故障磁盘二、移除故障磁盘三、添加备用磁盘四、监控数据重建过程五、验证RAID配置和数据完整性注意事项 使用 mdadm命令将故障磁盘替换为备用磁盘,并恢复数据的过程大致如下: 一、标记故障磁盘 检查RAID状态: 首先&#xf…

D74【 python 接口自动化学习】- python 基础之HTTP

day74 http基础定义 学习日期:20241120 学习目标:http定义及实战 -- http基础介绍 学习笔记: HTTP定义 HTTP 是一个协议(服务器传输超文本到浏览器的传送协议),是基于 TCP/IP 通信协议来传递数据&…

FPGA经验谈系列文章——8、复位的设计

前言 剑法往往有着固定的招式套路,而写代码似乎也存在类似的情况。不知从何时起,众多 FPGA 工程师们在编写代码时开启了一种关于 always 语句块的流行写法,那就是: always @(posedge i_clk or negedge i_rstn) 就笔者所经历的诸多项目以及所接触到的不少工程师而言,大家在…

STM32总体架构简单介绍

目录 一、引言 二、STM32的总体架构 1、三个被动单元 (1)内部SRAM (2)内部闪存存储器 (3)AHB到APB的桥(AHB to APBx) 2、四个主动(驱动)单元 &#x…

Day03_AJAX原理 (黑马笔记)

Day03_AJAX原理 目录 Day03_AJAX原理 学习目标 01.XMLHttpRequest - 基础使用 目标 讲解 小结 02.XMLHttpRequest - 查询参数 目标 讲解 小结 03.案例 - 地区查询 目标 讲解 小结 04.XMLHttpRequest - 数据提交 目标 讲解 小结 05.认识_Promise 目标 讲解…

selenium grid 远程webdriver添加上网代理

################## selenium grid config start ####################### # UI自动化测试策略 Grid/Local UIAutomation_TestStrategy Grid selenium_grid_url http://172.16.99.131:4444/wd/hub # Windows XP / linux grid_platformName linux # windows capabilities win…

Vue 动态给 data 添加新属性深度解析:问题、原理与解决方案

在 Vue 中,动态地向 data 中添加新的属性是一个常见的需求,但它也可能引发一些问题,尤其是关于 响应式更新 和 数据绑定 的问题。Vue 的响应式系统通过 getter 和 setter 来追踪和更新数据,但 动态添加新属性 时,Vue 并不会自动为这些新属性创建响应式链接。 1. 直接向 V…