什么是 Java 中的单例模式?如何实现?

单例模式是一种常用的软件设计模式,在这种模式中,某个类的实例化次数被限制为一个。意味着,在整个应用程序中,一个类只有一个实例存在。这用于那些我们只需要一个实例来控制操作的情况下,比如:日志、驱动对象、数据库操作等。

首先,让我们来看看一个最简单的单例模式的实现:

复制代码
public class Singleton {
  private static Singleton instance;
  public static synchronized Singleton getInstance() {
    if (instance == null) {
      instance = new Singleton();
    }
    return instance;
  }
  private Singleton() {}
}

在这个代码中,我们有一个Singleton类,它有一个Singleton类型的私有静态成员变量instance,同时有一个公有的静态方法getInstance。这个getInstance方法是获取Singleton实例的唯一途径。当我们第一次调用getInstance方法时,会创建一个Singleton的实例,并赋值给instance;而在后续的调用中,如果instance已经被创建,则直接返回。也就是说,Singleton类的实例最多只会被创建一次。

把构造函数设为私有的是非常关键的,这是为了防止在外部被new出新的实例。并且我们注意到,instance的创建过程被synchronized修饰,这是为了防止在多线程环境下生成多个实例。然后,是另外一种更为推荐的单例模式的实现方法,那就是使用Java的内部类来保证单例:

复制代码
public class Singleton {
    private Singleton() {}
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

在这个实现中,SingletonHolder是一个私有的静态内部类,当第一次加载Singleton类时不会初始化这个静态内部类,只有在第一次调用Singleton的getInstance方法时才会导致SingletonHolder被初始化。由于实例的建立是在类加载的时候完成,这样就可以确保线程安全。这个模式的优点是既保证了线程安全,又能做到延迟加载。

除了前面提到的最基础的单例模式,有很多种使用单例模式的方式,比如"懒汉式","饿汉式","双检锁/双重校验锁 (DCL,即 double-checked locking)","登记式/静态内部类","枚举"等。其中,"饿汉式"和"懒汉式"是最常用的两种形式。

"饿汉式"是最常见的一种实现方式,这种方式在类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快,它是一种典型的以时间换空间的方式。

复制代码
public class Singleton {
    private static Singleton instance = new Singleton();
    private Singleton (){}
    public static Singleton getInstance() {
        return instance;
    }
}

相对应的,"懒汉式"是另一种方式,较之前示例不同,懒汉式是典型的空间换时间的方式,只有在使用的时候才去创建对象,可以节约内存。

复制代码
public class Singleton {
    private volatile static Singleton singleton ;
    private Singleton (){}
    public static Singleton getSingleton() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

然后,还有一种方式是双检锁/双重校验锁(DCL,即 double-checked locking)。这种方式在加锁的情况下,我们再检查一次,如果单例仍然不是空,就返回实例,否则就创建一个新的实例。这是一种比较复杂的实现方式,但可以在保持线程安全的前提下,提高了单例的获取性能。

复制代码
public class Singleton {
    private volatile static Singleton instance;
    private Singleton (){}
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

在上述的代码中,第一次检查是为了不必要的同步,第二次检查则是再同步情况下进行,保证了只生成一个实例。

理解了Java中单例模式的原理和实现后,你就可以在自己的代码中灵活应用这种设计模式了。例如在应用日志、数据库连接池等需要全局唯一的资源的管理时就可以使用到。这也是你作为Java开发者必会的面试题之一。

相关推荐
Boilermaker19925 小时前
[Java 并发编程] Synchronized 锁升级
java·开发语言
Cherry的跨界思维5 小时前
28、AI测试环境搭建与全栈工具实战:从本地到云平台的完整指南
java·人工智能·vue3·ai测试·ai全栈·测试全栈·ai测试全栈
MM_MS5 小时前
Halcon变量控制类型、数据类型转换、字符串格式化、元组操作
开发语言·人工智能·深度学习·算法·目标检测·计算机视觉·视觉检测
꧁Q༒ོγ꧂6 小时前
LaTeX 语法入门指南
开发语言·latex
njsgcs6 小时前
ue python二次开发启动教程+ 导入fbx到指定文件夹
开发语言·python·unreal engine·ue
alonewolf_996 小时前
JDK17新特性全面解析:从语法革新到模块化革命
java·开发语言·jvm·jdk
一嘴一个橘子6 小时前
spring-aop 的 基础使用(啥是增强类、切点、切面)- 2
java
sheji34166 小时前
【开题答辩全过程】以 中医药文化科普系统为例,包含答辩的问题和答案
java
古城小栈6 小时前
Rust 迭代器产出的引用层数——分水岭
开发语言·rust
ghie90906 小时前
基于MATLAB的TLBO算法优化实现与改进
开发语言·算法·matlab