单例模式详解

乌市泽哥  金牌会员 | 前天 00:53 | 来自手机 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 567|帖子 567|积分 1701

Java 单例模式详解

1. 单例模式简介

单例模式(Singleton Pattern)是 Java 中最简单、常用的设计模式之一,属于创建型设计模式。其核心头脑是确保一个类只有一个实例,并提供一个全局的访问点来获取该实例。单例模式常用于资源管理(如数据库毗连池、线程池等),避免不必要的开销和资源浪费。
1.1 单例模式的主要脚色


  • 单例类:该类只答应创建一个实例,并负责控制实例的创建过程。在单例模式中,构造方法通常被声明为私有,以防止外部类直接通过 new 关键字创建对象。
  • 访问类:通过单例类提供的公共方法(通常是静态方法)获取单例对象的唯一实例。
1.2 单例模式的优缺点

优点:



  • 节约内存:确保内存中只存在一个实例,避免不必要的资源斲丧。
  • 全局访问:可以全局访问该实例,简化了系统中差别模块之间的通信。
  • 延迟加载(懒汉式):在需要的时候才创建对象,减少系统初始化的负担。
缺点:



  • 线程安全问题:在多线程环境中,如果实现不当,可能会导致创建多个实例,从而破坏单例模式的核心设计。
  • 反射与序列化破坏:单例模式在某些情况下可能会被反射或者序列化机制破坏,需要额外的防御步伐。
2. 单例模式的实现方式

单例模式的实现方式有多种,主要分为 饿汉式懒汉式。饿汉式实例化较早,懒汉式则延迟实例化。下面是几种常见的实现方式。
2.1 饿汉式

饿汉式在类加载时就会初始化单例实例,确保在第一次使用之前实例已经创建完成。饿汉式的优势在于实现简单,线程安全。但是如果实例比较大,而程序一直未使用该实例,则可能会造成内存浪费。
2.1.1 饿汉式(静态变量方式)

这是最常见的实现方式之一,实例随着类的加载而创建。这种方式的缺点是纵然你不使用实例,它也会被创建出来,导致资源的浪费。
  1. public class Singleton {
  2.     // 私有化构造方法,防止外部直接通过new创建对象
  3.     private Singleton() {}
  4.     // 静态变量,类加载时创建实例
  5.     private static Singleton instance = new Singleton();
  6.     // 对外提供获取实例的方法
  7.     public static Singleton getInstance() {
  8.         return instance;
  9.     }
  10. }
复制代码
说明


  • 在类加载的过程中,静态变量 instance 被初始化为 Singleton 类的对象。
  • 外部类通过调用 getInstance() 方法获取实例。
  • 这种方式的优点是简单,但缺点在于类加载时就创建了实例,无论是否使用该实例都会占用内存资源。
2.1.2 饿汉式(静态代码块方式)

静态代码块的方式与静态变量方式类似,差别点在于对象的创建是在静态代码块中举行。
  1. public class Singleton {
  2.     private Singleton() {}
  3.     // 静态变量,尚未赋值
  4.     private static Singleton instance;
  5.     // 静态代码块,类加载时创建实例
  6.     static {
  7.         instance = new Singleton();
  8.     }
  9.     public static Singleton getInstance() {
  10.         return instance;
  11.     }
  12. }
复制代码
说明


  • 类加载时实行静态代码块,创建实例。
  • 该方式与第一种方法的区别主要是将实例化操作放在静态代码块中,目的是分离变量声明和对象初始化。
  • 缺点依旧是可能造成内存浪费。
2.2 懒汉式

懒汉式相比饿汉式有一个显著优势,即实例是在真正需要时才创建,避免了内存浪费。但懒汉式在多线程环境中需要注意线程安全问题,否则可能出现多个线程同时创建多个实例的情况。
2.2.1 懒汉式(线程不安全)

这是懒汉式的根本实现,只有在调用 getInstance() 方法时才会创建实例。这种方式在单线程下是安全的,但在多线程环境下可能会导致多个实例的创建,违反单例原则。
  1. public class Singleton {
  2.     private Singleton() {}
  3.     // 静态变量,尚未赋值
  4.     private static Singleton instance;
  5.     // 对外提供获取实例的方法
  6.     public static Singleton getInstance() {
  7.         if (instance == null) {
  8.             instance = new Singleton(); // 第一次调用时才会创建实例
  9.         }
  10.         return instance;
  11.     }
  12. }
复制代码
说明


  • 在 getInstance() 方法中,只有当 instance 为 null 时才会创建实例,实现了懒加载。
  • 在多线程环境下,多个线程可能会同时进入 if (instance == null),导致创建多个实例,破坏单例模式。
2.2.2 懒汉式(线程安全)

为了解决多线程环境下的线程安全问题,可以在 getInstance() 方法上添加 synchronized 关键字,确保每次只有一个线程能够实行实例的创建过程。然而这种方式的缺点是加锁会导致性能下降。
  1. public class Singleton {
  2.     private Singleton() {}
  3.     private static Singleton instance;
  4.     // 对外提供获取实例的方法,使用synchronized保证线程安全
  5.     public static synchronized Singleton getInstance() {
  6.         if (instance == null) {
  7.             instance = new Singleton();
  8.         }
  9.         return instance;
  10.     }
  11. }
复制代码
说明


  • 通过 synchronized 关键字锁住整个 getInstance() 方法,确保线程安全。
  • 缺点是每次调用 getInstance() 方法时,都会举行同步操作,纵然实例已经创建,也会影响性能。
2.2.3 懒汉式(双重查抄锁)

为了优化同步锁的性能,可以接纳双重查抄锁机制(Double-Checked Locking),即在进入同步块之前和之后各举行一次 null 查抄,只有在实例为 null 时才进入同步块,减少了不必要的同步操作。
  1. public class Singleton {
  2.     private Singleton() {}
  3.     // volatile关键字确保多线程环境下的可见性和有序性
  4.     private static volatile Singleton instance;
  5.     public static Singleton getInstance() {
  6.         if (instance == null) { // 第一次检查
  7.             synchronized (Singleton.class) {
  8.                 if (instance == null) { // 第二次检查
  9.                     instance = new Singleton();
  10.                 }
  11.             }
  12.         }
  13.         return instance;
  14.     }
  15. }
复制代码
说明


  • 使用双重查抄锁,第一次判断避免了不必要的加锁操作。
  • 使用 volatile 关键字,防止由于指令重排序导致的线程安全问题。
  • 这种方式联合了懒加载和线程安全的优点,且性能较好。
2.2.4 懒汉式(静态内部类)

静态内部类的单例模式使用了 JVM 的类加载机制。JVM 会确保类加载过程中线程的安全性,因此无需显式加锁,同时实现了懒加载。
  1. public class Singleton {
  2.     private Singleton() {}
  3.     // 静态内部类,负责实例的创建
  4.     private static class SingletonHolder {
  5.         private static final Singleton INSTANCE = new Singleton();
  6.     }
  7.     public static Singleton getInstance() {
  8.         return SingletonHolder.INSTANCE;
  9.     }
  10. }
复制代码
说明


  • 该模式使用了 JVM 的类加载机制来确保线程安全。
  • 当 Singleton 类被加载时,内部类 SingletonHolder 不会立刻被加载,只有在第一次调用 getInstance() 方法时,JVM 才会加载 SingletonHolder,并创建 INSTANCE 实例。
  • 该实现方式保证了线程安全、懒加载,并且没有锁的开销,性能较好。
2.3 枚举方式

使用枚举类实现单例模式是尽力保举的方式之一,因为 Java 枚举类本身就是线程安全的,且只会被加载一次。枚举方式自然防止反序列化和反射攻击。
  1. public enum Singleton {
  2.     INSTANCE;
  3. }
复制代码
说明


  • 枚举类的特性保证了在多线程环境中的安全性。
  • 枚举单例是实现单例模式最简便、最安全的方式,同时可以防止反射和序列化攻击,因此被认为是最优的单例模式实现方式。
3. 破坏与防御

3.1 序列化破坏单例模式

通过序列化和反序列化可以破坏单例模式,导致创建多个实例。为了解
决该问题,可以在 Singleton 类中添加 readResolve() 方法,该方法在反序列化过程中被调用,确保返回已有的实例。
  1. private Object readResolve() {
  2.     return SingletonHolder.INSTANCE;
  3. }
复制代码
3.2 反射破坏单例模式

反射可以通过强制调用私有构造方法,创建多个实例,破坏单例模式。为防止反射攻击,可以在构造方法中加入判断,如果实例已经存在,则抛出异常。
  1. private Singleton() {
  2.     if (instance != null) {
  3.         throw new RuntimeException("单例模式被破坏");
  4.     }
  5. }
复制代码
4. 总结



  • 饿汉式:类加载时创建实例,简单但可能会浪费内存资源。
  • 懒汉式:实例延迟到第一次使用时才创建,节流资源,但需要处置惩罚线程安全问题。
  • 静态内部类:优雅的解决方案,联合了懒加载、线程安全和性能的优点。
  • 枚举方式:最保举的实现方式,简单、安全,且能防止反射和序列化破坏。
单例模式虽然简单,但在高并发环境下实现需要特别注意线程安全问题。同时,序列化和反射可能会破坏单例,需要采取额外的防御步伐,如 readResolve() 和反射保护机制。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

乌市泽哥

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表