【单例模式】饿汉模式和懒汉模式的单例模式

文章目录

  • 一、设计模式
  • 二、单例模式
    • 2.1 单例模式的概念与特点
    • 2.2 饿汉模式和懒汉模式
  • 三、单例模式的实现
    • 3.1 饿汉模式的单例模式
    • 3.2 懒汉模式的单例模式


一、设计模式

设计模式是一种在软件设计中经过验证的解决问题的方案或者模版。它们是从实践中总结出来的,可以帮助解决常见的设计问题,提高代码的重用性、维护性和扩展性。

设计模式可以分为三大类:

  1. 创建型模式(Creational Patterns):创建型模式关注对象的实例化过程,包括如何创建、组合和表示对象。常见的创建型模式有单例模式、工厂模式、抽象工厂模式、建造者模式和原型模式等。

  2. 结构型模式(Structural Patterns):结构型模式关注对象的组合和关系,以实现更大的结构和功能。常见的结构型模式有适配器模式、装饰器模式、代理模式、组合模式、享元模式和桥接模式等。

  3. 行为型模式(Behavioral Patterns):行为型模式关注对象之间的通信和协作,以实现特定的行为和交互模式。常见的行为型模式有观察者模式、策略模式、模板方法模式、迭代器模式、状态模式和命令模式等。

每种设计模式都有其特定的应用场景和优势,可以根据需求选择适当的模式。设计模式可以提供一些常用的解决方案,帮助开发人员更好地组织代码结构、降低耦合度、增加灵活性和可维护性

二、单例模式

2.1 单例模式的概念与特点

单例模式(Singleton Pattern)是一种创建型设计模式,用于确保一个类只存在一个实例,并提供全局访问点来访问该实例

在软件开发中,有些类只需要拥有一个全局实例,例如日志记录器、数据库连接池、线程池等。使用单例模式可以确保这些类只被实例化一次,从而节省系统资源,避免不必要的对象的创建和销毁

单例模式通常具有以下特点:

  1. 私有构造方法:单例类的构造方法是私有的,这样做的目的是为了防止外部代码直接实例化该类对象
  2. 静态访问方法:单例模式提供一个静态方法,用于获取单例实例。这个方法通常被命名为getInstance()
  3. 线程安全性:单例模式需要考虑多线程环境下的线程安全问题。确保在多线程环境下获取单例实例的方法是线程安全的,可以使用同步机制和双重检查锁(Double-Checked Locking)等方式实现。
  4. 全局访问:单例模式提供单例实例的全局访问,其他代码可以通过调用单例类的访问方法来获取实例。

单例模式可以带来一些优点,如减少内存开销、统一管理全局资源、提供全局访问点等。然而,过度使用单例模式可能导致代码的可测试性和可扩展性下降,因此在设计和应用中需要谨慎使用。

2.2 饿汉模式和懒汉模式

饿汉模式(Eager Initialization)懒汉模式(Lazy Initialization) 是两种常见的单例模式实现方式。

饿汉模式:

  • 在饿汉模式中,单例实例在类加载时就被创建,并在整个程序运行期间保持不变。因此,它的实例化是立即发生的,不需要延迟加载。
  • 饿汉模式的实现简单直接,可以通过将构造函数设为私有并创建一个静态的、final的实例来实现。通过静态方法或直接访问实例变量,其他代码可以获得该实例。
  • 饿汉模式是线程安全的,因为实例在类加载时就被创建,不存在多线程环境下的竞争条件
  • 由于实例在类加载时就创建,可能会导致资源的浪费,尤其是在实例很大或者需要耗费较多资源的情况下

懒汉模式:

  • 在懒汉模式中,单例实例在第一次使用时才被创建,延迟实例化。因此,它的实例化是在需要时发生的,具有延迟加载的特性。
  • 懒汉模式的实现通常涉及使用双重检查锁机制或者使用内部类来实现延迟加载和线程安全
  • 懒汉模式可以避免在应用程序启动时就创建实例,节省了资源。然而,在多线程环境中需要考虑线程安全性,特别是在第一次创建实例时需要进行同步控制,以避免多个线程同时创建多个实例的问题。

根据以上的定义可以得出,饿汉模式适用于实例创建和初始化的成本较低、资源占用较小的情况懒汉模式适用于实例创建和初始化的成本较高、资源占用较大的情况,并且希望在需要时才进行实例化。同时,在多线程环境下,需要考虑线程安全性和性能等因素进行选择。

三、单例模式的实现

3.1 饿汉模式的单例模式

恶汉模式的代码如下:

class Singleton{
    // 此处,先把这个实例创建出来
    private static Singleton instance = new Singleton();

    // 如果要使用这个实例,统一通过 Singleton.getInstance() 方式获取
    // 饿汉模式只涉及到读操作,因此是线程安全的
    public static Singleton getInstance(){
        return instance;
    }

    // 为了避免 Singleton 类不小心被复制出多份
    // 把构造方法设置为 private,在类外面就无法通过 new 的方式来创建这个 Singleton 实例了
    private Singleton(){}
}
  • 在这个实现中,单例实例 instance 在类加载阶段就被创建,并且通过静态方法 getInstance() 返回该实例。

  • 饿汉模式的实现确保了在整个程序运行期间只有一个实例存在,并且在需要时立即获得该实例。由于实例在类加载时就被创建,所以在多线程环境下也是线程安全的。

  • 私有的构造方法确保了在类外部无法通过 new 关键字创建该类的实例,从而限制了实例的数量。

3.2 懒汉模式的单例模式

单线程版懒汉模式的代码实现:

class Singleton {
    private static Singleton instance = null;

    private Singleton(){}

    public static Singleton getInstance() {
        if(instance == null){
        	instance = new Singleton();
        }
        return instance;
    }
}

上面的代码实现了单线程环境下的懒汉模式单例模式。

在这个实现中,getInstance() 方法首先检查 instance 是否为 null,如果为 null,则通过私有的构造函数创建一个新的实例,并将其赋值给 instance 变量。在后续调用 getInstance() 方法时,由于 instance 已经被赋值,直接返回该实例。

这种实现方式在单线程环境下是可行的,能够保证只有一个实例被创建并被返回。但是在多线程环境下,可能会出现问题,多个线程同时通过 instance == null 的判断条件进入,导致多次创建实例,违反了单例模式的原则。

因此,在多线程环境下,需要对该实现进行改进,以保证线程安全性。常见的方式是引入同步机制,如双重检查锁或使用 synchronized 关键字,来确保只有一个线程能够创建实例。

多线程版:

class Singleton{

    // 存在指令重排序问题。
    // 加上 volatile 关键字保持 instance 内存可见性,禁止指令重排
    private volatile static Singletoninstance = null;

    // 懒汉模式存在现线程安全
    // 解决方法:加上 synchronized
    // 存在的问题:这里的加锁只是在new对象之前加锁才是有必要的,后面就一直是读操作,没有加锁的必要

    // 改进:
    // 使用双重 if 判定, 降低锁竞争的频率
    // 给 instance 加上了 volatile.

    public static Singleton getInstance() {
        if (null == instance) { // 判断是否加锁
            // 当 instance 为空时才加锁
            synchronized (Singleton.class) {
                if (null == instance) { // 判断是否创建对象
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
    
    private Singleton() {
    }
}

以上代码实现了多线程环境下的懒汉模式单例模式,并对其进行了改进。

在这个实现中,使用了 双重检查锁(Double-Checked Locking) 机制和 volatile 关键字来解决多线程环境下的线程安全和指令重排序问题。

instance 声明为 volatile,可以保证线程之间对 instance 的可见性和禁止指令重排序,避免在实例化过程中的并发问题。因为在早期的 Java 版本中存在双重检查锁失效的问题。为了避免这个问题,建议使用 Java 5 及以上版本,并将 instance 变量声明为 volatile

你可能感兴趣的:(Java进阶,单例模式,java)