# 单例模式(Singleton Pattern)

# 概念

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

# 作用

1.资源优化:避免频繁创建和销毁实例,节省系统资源,提高性能,常用于资源消耗较大的对象,如数据库连接池。
2.全局访问:提供全局访问点,方便在系统各处获取和使用实例,避免传递实例的繁琐。
3.控制实例创建 :严格控制实例的生成,确保某些关键业务逻辑只有一个入口。

# 场景

1.资源共享:如打印机、线程池等资源,避免资源冲突和浪费。
2.配置管理:系统配置信息,确保统一读取和管理配置。
3.日志记录:日志系统,集中管理日志记录,避免多实例导致混乱。
4.缓存管理:缓存系统,统一管理和操作缓存数据。

# 饿汉式(线程安全)

定义单例类:

package net.feixiang.creational.singleton.eager;

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

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

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

    /**
     * 示例业务方法
     */
    public void doSomething() {
        System.out.println("EagerSingleton:Hello FEIXIANG!");
    }
}

运行示例:

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());
        }
    }
}

控制台输出:

EagerSingleton:Hello FEIXIANG!
1694819250
EagerSingleton:Hello FEIXIANG!
1694819250
EagerSingleton:Hello FEIXIANG!
1694819250

# 解析

# 为什么叫“饿汉式”?


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

# 为什么线程安全?


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

# 怎么判断是同一个实例?


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

# staticfinal的作用


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

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

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

结合使用的效果:

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

# private EagerSingleton() {}的作用


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

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

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

# public static EagerSingleton getInstance()的作用


提供一个公共的静态方法,用于获取单例实例。

通过这个方法,外部代码可以访问唯一的单例实例,而不需要知道实例的创建细节。

# 懒汉式(线程不安全)

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

# 1.资源优化

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

# 2.延迟初始化

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

# 3.提高启动速度

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

# 懒汉式单例模式示例

/**
 * 懒汉式单例类(线程不安全)
 * 特点:延迟初始化(在第一次使用时创建实例)
 */
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("LazySingleton1:Hello FEIXIANG!");
    }
}

# 为什么懒汉式单例类是线程不安全的


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

示例说明

假设两个线程同时调用 getInstance() 方法:

1.线程 A 和 线程 B 同时调用 getInstance()。

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

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

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

如何处理线程安全问题

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

详细解释

  1. synchronized 关键字:

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

当 线程 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("LazySingleton2:Hello FEIXIANG!");
    }
}

# 静态内部类实现

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("StaticClassSingleton:Hello FEIXIANG!");
    }
}

# 枚举实现

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

package net.feixiang.creational.singleton.constant;

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

    /**
     * 示例业务方法
     */
    public void doSomething() {
        System.out.println("EnumSingleton:Hello FEIXIANG!");
    }
}

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("EnumSingleton:Hello FEIXIANG!");
    }
}

总结

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



微信公众号

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

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

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

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