# 单例模式

# 概念

单例模式(Singleton Pattern)是一种创建型设计模式,确保一个类只有一个实例,并提供一个全局访问点。

# 作用

  1. 资源优化:避免频繁创建和销毁实例,节省系统资源,提高性能,常用于资源消耗较大的对象,如数据库连接池。

  2. 全局访问:提供全局访问点,方便在系统各处获取和使用实例,避免传递实例的繁琐。

  3. 控制实例创建 :严格控制实例的生成,确保某些关键业务逻辑只有一个入口。

# 场景

  1. 资源共享:如打印机、线程池等资源,避免资源冲突和浪费。

  2. 配置管理:系统配置信息,确保统一读取和管理配置。

  3. 日志记录:日志系统,集中管理日志记录,避免多实例导致混乱。

  4. 缓存管理:缓存系统,统一管理和操作缓存数据。

# 饿汉式(线程安全)

定义单例类:

package net.feixiang.creational.singleton.eager;

/**
 * 饿汉式单例类。特点:类加载时立即初始化实例(非延迟加载)。
 */
public class EagerSingleton {
    // 静态常量在类加载时直接初始化实例
    // final 保证实例不可变,static 保证实例在类级别唯一性
    // 线程安全:由 JVM 类加载机制保证(天然线程安全)
    private static final EagerSingleton instance = new EagerSingleton();

    /**
     * 私有构造函数
     * 作用:阻止外部通过 new 关键字创建实例
     */
    private EagerSingleton() {}

    /**
     * 获取单例实例的全局访问点
     * @return 已预先创建的单例实例
     */
    public static EagerSingleton getInstance() {
        // 直接返回类加载时已初始化的实例
        return instance;
    }

    /**
     * 示例业务方法
     */
    public void doSomething() {
        System.out.println("饿汉式单例模式:你好,飞翔!");
    }
}

运行示例:

package net.feixiang.creational.singleton.eager;

/**
 * 调用饿汉式单例类的示例程序
 */
public class EagerSingletonDemo {
    public static void main(String[] args) {
        // 获取单例实例
        EagerSingleton singleton = EagerSingleton.getInstance();
        // 调用单例实例的方法
        for (int i = 0; i < 3; i++) {
            singleton.doSomething();
            // 输出单例实例的哈希码以判断是否是同一个实例
            System.out.println(singleton.hashCode());
        }
    }
}

控制台输出:

饿汉式单例模式:你好,飞翔!
1694819250
饿汉式单例模式:你好,飞翔!
1694819250
饿汉式单例模式:你好,飞翔!
1694819250

# 解析


  1. 为什么叫“饿汉式”?

    “饿汉式”这个名字来源于它的行为特点。在饿汉式单例模式中,实例在类加载时就被创建,就好像一个饥饿的人(饿汉)迫不及待地吃掉食物一样。它在类加载时就“吃掉”了资源,确保了实例的唯一性和线程安全性。

  2. 为什么线程安全?

    饿汉式单例模式的线程安全性主要依赖于 Java 的类加载机制。在 Java 中,类的静态变量在类加载时被初始化,并且类加载过程是由 JVM 保证线程安全的。因此,当多个线程同时调用 getInstance() 方法时,由于实例已经在类加载时被创建,所以不会出现多次创建实例的问题。

  3. 怎么判断是同一个实例?

    在示例代码中,通过输出单例实例的哈希码( hashCode )可以判断是否是同一个实例。由于饿汉式单例模式在类加载时就创建实例,因此多次调用 getInstance() 方法返回的都是同一个实例,其哈希码也是相同的。

  4. staticfinal 的作用

    static 用于声明一个静态变量 instance ,它属于类本身而不是类的某个实例。静态变量在类加载时被初始化,并且在内存中只有一份副本,所有类的实例共享这个变量。

    在单例模式中, static 确保 instance 是类级别的变量,而不是实例级别的变量,这样可以保证只有一个实例存在。

    final 用于声明一个最终变量 instance ,它的值一旦被初始化后不能被修改。

    在单例模式中, final 确保 instance 只能被赋值一次,从而保证单例的唯一性。

    结合使用的效果——确保只有一个 new Singleton() 。通过 staticfinal 的结合, instance 在类加载时被初始化,并且只能被赋值一次。这样就确保了在整个应用程序中,只有一个 Singleton 实例被创建。

  5. private EagerSingleton() {} 的作用

    将构造方法声明为私有,防止外部通过 new Singleton() 的方式创建类的实例。

    在单例模式中,我们希望类的实例只能通过 getInstance() 方法获取,而不是通过直接调用构造方法创建。

    如果没有私有构造方法,外部代码可以通过 new Singleton() 创建多个实例,破坏单例模式的唯一性。

  6. public static EagerSingleton getInstance() 的作用

    提供一个公共的静态方法,用于获取单例实例。通过这个方法,外部代码可以访问唯一的单例实例,而不需要知道实例的创建细节。

# 懒汉式(线程不安全)

# 为什么需要懒汉式单例模式?


  1. 资源优化

    如果实例创建成本较高(如数据库连接池、大型对象等),而实例可能在整个应用程序运行过程中都不被使用,那么饿汉式会在类加载时就创建实例,造成资源浪费。懒汉式则在第一次使用时才创建实例,避免了这种浪费。

  2. 延迟初始化

    在某些情况下,实例的初始化可能依赖于某些运行时条件或配置。懒汉式可以在满足这些条件时才初始化实例,从而提高系统的灵活性。

  3. 提高启动速度

    如果实例的初始化过程较复杂且耗时,而应用程序在启动时并不立即需要该实例,那么懒汉式可以延迟初始化,从而提高应用程序的启动速度。

# 懒汉式单例模式示例


定义单例类:

package net.feixiang.creational.singleton.lazy;

/**
 * 懒汉式单例类(线程不安全)。特点:延迟初始化(在第一次使用时创建实例)。
 */
public class LazySingleton1 {
    // 静态变量用于保存唯一实例(未直接初始化)
    private static LazySingleton1 instance;

    /**
     * 私有构造函数
     * 作用:阻止外部通过 new 关键字直接创建实例
     */
    private LazySingleton1() {}

    /**
     * 获取单例实例的全局访问点(可能存在多线程同时访问的情况)
     * @return 单例实例
     */
    public static LazySingleton1 getInstance() {
        // 使用时才检查实例是否已创建,未创建则创建实例再返回
        if (instance == null) {
            instance = new LazySingleton1();
        }
        return instance;
    }

    /**
     * 示例业务方法
     */
    public void doSomething() {
        System.out.println("懒汉式(线程不安全)单例模式:你好,飞翔!");
    }
}

运行示例:

package net.feixiang.creational.singleton.lazy;

/**
 * 调用饿汉式单例类的示例程序:线程不安全
 */
public class LazySingletonDemo1 {
    public static void main(String[] args) {
        // 获取单例实例
        LazySingleton1 singleton = LazySingleton1.getInstance();
        // 调用单例实例的方法
        for (int i = 0; i < 3; i++) {
            singleton.doSomething();
            // 输出单例实例的哈希码以判断是否是同一个实例
            System.out.println(singleton.hashCode());
        }
    }
}

控制台输出:

懒汉式(线程不安全)单例模式:你好,飞翔!
1694819250
懒汉式(线程不安全)单例模式:你好,飞翔!
1694819250
懒汉式(线程不安全)单例模式:你好,飞翔!
1694819250
  1. 为什么懒汉式单例类是线程不安全的?

    上面方法是线程不安全的,因为在多线程环境下,可能会有多个线程同时通过 if (instance == null) 检查,从而导致多次创建 Singleton 实例。

    示例说明,假设两个线程同时调用 getInstance() 方法:

    • 线程 A 和 线程 B 同时调用 getInstance()

    • 2.线程 A 和 线程 B 同时检查 if (instance == null) ,发现 instancenull

    • 3.线程 A 和 线程 B 都进入 if 块,分别创建 Singleton 实例。

    • 4.最终,Singleton 类被实例化了两次,破坏了单例模式的唯一性。

  2. 如何处理线程安全问题?

    为了确保线程安全,可以使用 synchronized 关键字来同步 getInstance() 方法。这样可以确保在同一时间只有一个线程可以进入 getInstance() 方法,从而避免多个线程同时创建实例。

    2.1 详细解释

    • synchronized 关键字

      synchronized 用于确保同一时间只有一个线程可以进入被修饰的方法或代码块。 在 getInstance() 方法上使用 synchronized ,可以确保在多线程环境下,只有一个线程可以执行 getInstance() 方法的代码块,从而避免多个线程同时创建实例。

    • 线程安全的实现

      当 线程 A 进入 getInstance() 方法时,其他线程(如 线程 B)会被阻塞,等待 线程 A 完成实例化。线程 B 在进入 getInstance() 方法时,instance 已经被 线程 A 创建,因此不会再次创建实例。

定义单例类:

package net.feixiang.creational.singleton.lazy;

/**
 * 懒汉式单例类(线程安全)。特点:延迟初始化(在第一次使用时创建实例)。
 */
public class LazySingleton2 {
    // 静态变量用于保存唯一实例(未直接初始化)
    private static LazySingleton2 instance;

    /**
     * 私有构造函数
     * 作用:阻止外部通过 new 关键字直接创建实例
     */
    private LazySingleton2() {}

    /**
     * 获取单例实例的全局访问点(线程安全版本)
     * @return 单例实例
     */
    public static synchronized LazySingleton2 getInstance() {
        // 使用时才检查实例是否已创建,未创建则创建实例再返回
        if (instance == null) {
            instance = new LazySingleton2();
        }
        return instance;
    }

    /**
     * 示例业务方法
     */
    public void doSomething() {
        System.out.println("懒汉式(线程安全)单例模式:你好,飞翔!");
    }
}

运行示例:

package net.feixiang.creational.singleton.lazy;

/**
 * 调用饿汉式单例类的示例程序:线程安全
 */
public class LazySingletonDemo2 {
    public static void main(String[] args) {
        // 获取单例实例
        LazySingleton2 singleton = LazySingleton2.getInstance();
        // 调用单例实例的方法
        for (int i = 0; i < 3; i++) {
            singleton.doSomething();
            // 输出单例实例的哈希码以判断是否是同一个实例
            System.out.println(singleton.hashCode());
        }
    }
}

控制台输出:

懒汉式(线程安全)单例模式:你好,飞翔!
1694819250
懒汉式(线程安全)单例模式:你好,飞翔!
1694819250
懒汉式(线程安全)单例模式:你好,飞翔!
1694819250

# 静态内部类实现

  1. 线程安全

    静态内部类的加载是由 JVM 控制的, JVM 会确保在类初始化时只有一个线程能够执行初始化操作,因此这种实现方式是线程安全的。

  2. 懒加载

    静态内部类 SingletonHolder 只有在第一次调用 getInstance() 方法时才会被加载,从而初始化 Singleton 实例。这实现了懒加载,避免了在类加载时就初始化实例。

  3. 性能优化

    不需要使用 synchronized 关键字来保证线程安全,因此在实例创建后,多次调用 getInstance() 方法时不会有同步开销。

  4. 简洁性

    实现相对简洁,避免了复杂的同步逻辑。

定义单例类:

package net.feixiang.creational.singleton.staticClass;

/**
 * 静态内部类单例类。特点:结合懒加载与线程安全的优雅实现(推荐方案)。
 */
public class StaticClassSingleton {
    /**
     * 私有构造函数
     * 作用:阻止外部通过 new 关键字创建实例
     */
    private StaticClassSingleton() {}

    /**
     * 静态内部类持有单例实例
     * 特性:内部类在第一次被访问时才会加载(实现延迟初始化)
     */
    private static class SingletonHolder {
        // JVM 保证类加载过程的线程安全性
        // static 确保实例唯一
        // final 确保实例不可变
        private static final StaticClassSingleton instance = new StaticClassSingleton();
    }

    /**
     * 获取单例实例的全局访问点
     * 优势:无需同步锁,兼顾性能与线程安全
     * @return 单例实例(延迟初始化)
     */
    public static StaticClassSingleton getInstance() {
        // 触发内部类加载(此时才会初始化实例)
        return SingletonHolder.instance;
    }

    /**
     * 示例业务方法
     */
    public void doSomething() {
        System.out.println("静态内部类单例模式:你好,飞翔!");
    }
}

运行示例:

package net.feixiang.creational.singleton.staticClass;

/**
 * 调用静态内部类单例类的示例程序
 */
public class StaticClassSingletonDemo {
    public static void main(String[] args) {
        // 获取单例实例
        StaticClassSingleton singleton = StaticClassSingleton.getInstance();
        // 调用单例实例的方法
        for (int i = 0; i < 3; i++) {
            singleton.doSomething();
            // 输出单例实例的哈希码以判断是否是同一个实例
            System.out.println(singleton.hashCode());
        }
    }
}

控制台输出:

静态内部类单例模式:你好,飞翔!
1365202186
静态内部类单例模式:你好,飞翔!
1365202186
静态内部类单例模式:你好,飞翔!
1365202186

静态内部类实现单例模式是一种优雅的实现方式,它结合了饿汉式和懒汉式的优势,既保证了线程安全,又实现了懒加载,同时避免了同步带来的性能开销。推荐在需要线程安全且希望懒加载的场景中使用这种实现方式。

# 枚举实现

枚举实现单例模式是一种简洁且线程安全的方式,它利用了 Java 枚举类型的特性来确保单例的唯一性和线程安全性。以下是枚举实现单例模式的详细解析:

  1. 线程安全

    Java 枚举类型的实例创建是线程安全的,JVM 会确保在类加载时只有一个实例被创建。 枚举的实例化过程是由 JVM 控制的,因此不需要额外的同步措施。

  2. 防止反序列化

    枚举类型在反序列化时不会创建新的实例,因此可以防止通过反序列化破坏单例模式。

  3. 简洁性

    枚举实现单例模式的代码非常简洁,不需要额外的 getInstance() 方法或其他复杂的逻辑。

  4. 唯一性

    枚举类型在 Java 中是 final 的,不能被继承,因此可以确保只有一个实例存在。

定义单例类:

package net.feixiang.creational.singleton.constant;

/**
 * 枚举单例模式实现(推荐方案)。特点:最简洁安全的单例实现方式(Effective Java 作者推荐)。
 */
public enum EnumSingleton {
    // 单例实例
    INSTANCE;

    /**
     * 示例业务方法
     */
    public void doSomething() {
        System.out.println("枚举单例模式:你好,飞翔!");
    }
}

运行示例:

package net.feixiang.creational.singleton.constant;

/**
 * 调用枚举单例模式的示例程序
 */
public class EnumSingletonDemo {
    public static void main(String[] args) {
        // 获取单例实例
        EnumSingleton singleton = EnumSingleton.INSTANCE;
        // 调用单例实例的方法
        for (int i = 0; i < 3; i++) {
            singleton.doSomething();
            // 输出单例实例的哈希码以判断是否是同一个实例
            System.out.println(singleton.hashCode());
        }
    }
}

控制台输出:

枚举单例模式:你好,飞翔!
1694819250
枚举单例模式:你好,飞翔!
1694819250
枚举单例模式:你好,飞翔!
1694819250

枚举实现单例模式是一种简洁、线程安全且防止反序列化的单例实现方式。它利用了 Java 枚举类型的特性,确保了单例的唯一性和线程安全性。推荐在需要线程安全且防止反序列化的场景中使用这种实现方式。


总结


单例模式是创建型设计模式,可优化资源、提供全局访问点并控制实例创建。
实现方式有:饿汉式(类加载时初始化,线程安全);懒汉式(线程不安全,首次使用时创建);线程安全的懒汉式(用 synchronized 同步);静态内部类实现(懒加载且线程安全);枚举实现(简洁安全,推荐)。



微信公众号

QQ交流群
原创网站开发,偏差难以避免。

如若发现错误,诚心感谢反馈。

愿你倾心相念,愿你学有所成。

愿你朝华相顾,愿你前程似锦。