【设计模式系列笔记】单例模式

本文涉及的产品
云数据库 RDS SQL Server,基础系列 2核4GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云原生数据库 PolarDB 分布式版,标准版 2核8GB
简介: 单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点,以便全局范围内访问这个实例。单例模式的目标是限制一个类的实例化,确保在整个应用程序中只有一个实例存在,并提供对这个唯一实例的全局访问点。这对于控制对资源的访问、限制特定类的实例数量等场景非常有用。

1、单例模式介绍

单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点,以便全局范围内访问这个实例。单例模式的目标是限制一个类的实例化,确保在整个应用程序中只有一个实例存在,并提供对这个唯一实例的全局访问点。这对于控制对资源的访问、限制特定类的实例数量等场景非常有用。

以下是单例模式的关键思想和实现方式:

2、关键思想:

  1. 私有构造方法: 单例模式的类通常会将其构造方法设置为私有的,以防止外部直接创建实例。
  2. 私有静态实例: 单例类内部会维护一个私有的静态实例,确保全局唯一性。
  3. 公有静态方法: 提供一个公有的静态方法,用于获取该类的实例,如果实例不存在,则创建一个新的实例。

3、实现方式:

1. 饿汉式(Eager Initialization):

public class Singleton {
    // 私有静态实例,在类加载时就创建
    private static final Singleton instance = new Singleton();
    // 私有构造方法,防止外部直接实例化
    private Singleton() {}
    // 公有静态方法,提供全局访问点
    public static Singleton getInstance() {
        return instance;
    }
}

在饿汉式单例模式中,实例在类加载时就被创建,无论后续是否需要使用。这种方式保证了在多线程环境中也能正确地返回同一个实例,因为在类加载时,JVM 会保证只加载一次。

优点:

  1. 线程安全(Thread Safe): 饿汉式是线程安全的,因为实例在类加载时就创建,且创建过程只发生一次。
  2. 简单直观: 实现简单,代码清晰。没有锁、双重检查等复杂性。

缺点:

  1. 资源浪费: 在应用程序启动时就创建实例,如果这个实例在整个应用程序的生命周期内很少被使用,可能会浪费资源。
  2. 可能引起加载时长: 如果单例的初始化操作比较复杂,可能会导致应用程序启动时间较长。
  3. 不支持延迟加载: 由于实例在类加载时就创建,无法实现延迟加载,即在需要使用时再进行初始化。

使用场景:

  1. 资源消耗较小: 当单例实例的创建和初始化资源消耗较小,且在应用程序启动时就需要使用时,饿汉式是一个不错的选择。
  2. 简单的单例实例: 当单例类的实例较为简单,并且在应用程序的整个生命周期中都会被频繁使用时,饿汉式是一个简单直观的实现方式。
  3. 线程安全要求高: 如果应用程序对线程安全性要求较高,而且对资源的浪费能够接受,饿汉式可以考虑使用。

总体来说,饿汉式适用于在应用程序启动时就需要使用的场景,尤其是对线程安全性要求较高的情况。如果单例实例的初始化操作相对较小且能够接受在应用程序启动时就占用资源,饿汉式是一个简单有效的选择。

实际应用

饿汉式单例模式在许多Java框架和应用中都有广泛应用。一般来说,饿汉式适用于在应用程序启动时就需要使用的场景,对于一些常驻内存的对象或资源的管理,以及对线程安全性要求较高的情况。

在某些框架和库的源代码中,你可能会看到类似于饿汉式的实现,确保在框架初始化或加载时,某些对象或资源就已经准备好使用。以下是一些例子:

  1. Spring框架:
  • 在Spring框架中,一些核心组件或上下文对象可能使用饿汉式单例模式,确保在应用程序启动时就创建并准备好。这有助于提高性能,避免在运行时动态创建这些对象。
  1. Hibernate框架:
  • Hibernate框架中的一些组件,如SessionFactory(数据库连接池)的实例,可能也采用了饿汉式单例模式,以在应用程序启动时初始化并在整个应用程序生命周期内共享。
  1. Log4j日志库:
  • Log4j日志库中的一些核心组件,如LoggerFactory,也可能使用了饿汉式单例模式,确保在应用程序启动时就准备好日志工具。

这些是一些例子,实际上,在许多框架和应用中,饿汉式单例模式都可以用于确保在应用程序启动时就创建并初始化一些对象,以提高性能和资源管理效率。

2. 懒汉式(Lazy Initialization):

public class Singleton {
    // 私有静态实例,使用时才创建
    private static Singleton instance;
    // 私有构造方法,防止外部直接实例化
    private Singleton() {}
    // 公有静态方法,使用时创建实例
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

在懒汉式单例模式中,实例在需要时才被创建,延迟初始化。这种方式避免了在应用程序启动时就创建实例,提高了资源利用效率。但需要注意在多线程环境下可能需要额外的同步措施,以确保线程安全。

优点:

  1. 延迟加载(Lazy Loading): 实例在需要时才被创建,避免了应用程序启动时就创建所有实例的开销,提高了资源利用效率。
  2. 节省资源: 对于不总是被使用的对象,避免了在应用程序启动时就占用资源。

缺点:

  1. 线程安全性: 在多线程环境下,如果不加额外的同步措施,可能会导致多个线程并发访问时创建多个实例,破坏单例的特性。
  2. 复杂性增加: 为了确保线程安全,可能需要在代码中添加同步措施,增加了代码复杂性。
  3. 可能的性能问题: 在多线程环境下,由于同步措施可能引入性能问题,特别是在高并发的情况下。

使用场景:

  1. 资源敏感的对象: 对于一些资源敏感的对象,可以使用懒汉式以避免在应用程序启动时就占用过多的资源。
  2. 延迟加载的需求: 当单例对象的初始化较为复杂,或者初始化操作不是一开始就需要执行的,可以使用懒汉式延迟加载。
  3. 对性能要求较低的场景: 如果在应用程序启动时占用一定资源不会影响性能,而且确保线程安全可以比较容易地实现,可以考虑使用懒汉式。

需要注意的是,在多线程环境下,懒汉式可能需要加锁或使用其他同步措施以确保线程安全。在实际应用中,可以考虑使用双重检查锁定(Double-Checked Locking)或者使用静态内部类等方式来实现更高效和安全的懒汉式单例模式。

3. 双重检查锁定(Double-Checked Locking):

public class Singleton {
    // 私有静态实例,使用时才创建
    private static volatile Singleton instance;
    // 私有构造方法,防止外部直接实例化
    private Singleton() {}
    // 公有静态方法,使用时创建实例
    public static Singleton getInstance() {
      // 检查实例是否为 null(第一次检查)
        if (instance == null) {
            // 使用同步块确保原子性,避免竞态条件
            synchronized (Singleton.class) {
                // 双重检查实例是否仍然为 null(第二次检查)
                if (instance == null) {
                    // 如果仍然为 null,则创建一个新的 Singleton 实例
                    instance = new Singleton();
                }
            }
        }
        // 返回 Singleton 实例
        return instance;
    }
}

在双重检查锁定单例模式中,通过在同步块内外都进行一次检查,以减小同步的粒度,从而提高性能。这种模式主要用于在多线程环境中创建单例对象。

优点:

  1. 延迟加载(Lazy Loading): 与饿汉式相比,双重检查锁定允许实现延迟加载,只在需要时创建实例。
  2. 线程安全: 通过使用同步块和双重检查,可以确保在多线程环境下创建唯一的单例实例。
  3. 提高性能: 相较于直接在方法上加锁的懒汉式,双重检查锁定在减小同步块粒度的基础上,提高了性能。

缺点:

  1. 复杂性增加: 相较于饿汉式和简单的懒汉式,双重检查锁定的实现相对复杂,需要注意线程安全性和正确的双重检查逻辑。
  2. 可能的问题: 在早期Java版本中,由于JVM的指令重排序,可能导致双重检查锁定出现问题。但在Java 5及以后的版本中,通过使用volatile关键字可以解决这个问题。

使用场景:

  1. 需要延迟加载的场景: 当单例对象的初始化比较耗时,或者只在需要时才创建时,双重检查锁定是一个合适的选择。
  2. 对性能要求较高的场景: 在高并发情况下,双重检查锁定可以提高性能,减小同步的粒度。
  3. 要求线程安全且延迟加载的场景: 当在多线程环境下需要确保单例对象的唯一性,并且希望在需要时才进行实例化时,双重检查锁定是一种合适的设计。

需要注意的是,在Java 5及以后的版本中,使用volatile关键字修饰单例对象的引用可以确保在多线程环境中的可见性,避免了指令重排序导致的问题。因此,在实现双重检查锁定时,建议使用volatile修饰单例对象的引用。。

实际应用

Java.lang包下是Runtime

public class Runtime {
    private static final Runtime currentRuntime = new Runtime();
    private static Version version;
    /**
     * Returns the runtime object associated with the current Java application.
     * Most of the methods of class {@code Runtime} are instance
     * methods and must be invoked with respect to the current runtime object.
     *
     * @return  the {@code Runtime} object associated with the current
     *          Java application.
     */
    public static Runtime getRuntime() {
        return currentRuntime;
    }
    /** Don't let anyone else instantiate this class */
    private Runtime() {}
  ....
}
目录
相关文章
|
8天前
|
设计模式 Java API
Kotlin教程笔记(50) - 改良设计模式 - 工厂模式
Kotlin教程笔记(50) - 改良设计模式 - 工厂模式
|
8天前
|
设计模式 监控 Java
Kotlin教程笔记(52) - 改良设计模式 - 观察者模式
Kotlin教程笔记(52) - 改良设计模式 - 观察者模式
|
8天前
|
设计模式 安全 Java
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
|
4天前
|
设计模式 安全 Java
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
|
4天前
|
设计模式 Java Kotlin
Kotlin教程笔记(56) - 改良设计模式 - 装饰者模式
Kotlin教程笔记(56) - 改良设计模式 - 装饰者模式
|
4天前
|
设计模式 监控 Java
Kotlin教程笔记(52) - 改良设计模式 - 观察者模式
Kotlin教程笔记(52) - 改良设计模式 - 观察者模式
14 3
|
4天前
|
设计模式 算法 Kotlin
Kotlin教程笔记(53) - 改良设计模式 - 策略模式
Kotlin教程笔记(53) - 改良设计模式 - 策略模式
17 2
|
4天前
|
设计模式 安全 Java
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
14 1
|
4天前
|
设计模式 Java API
Kotlin教程笔记(50) - 改良设计模式 - 工厂模式
Kotlin教程笔记(50) - 改良设计模式 - 工厂模式
11 1
|
16天前
|
设计模式 监控 Java
Kotlin教程笔记(52) - 改良设计模式 - 观察者模式
Kotlin教程笔记(52) - 改良设计模式 - 观察者模式
32 9