单例模式是一种创建型设计模式,确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。在 Java 中,有多种实现单例模式的方式,下面将详细介绍常见的实现方式及其优缺点。
1. 饿汉式(静态常量)
public class Singleton {
// 静态常量,在类加载时就创建实例
private static final Singleton INSTANCE = new Singleton();
// 私有构造函数,防止外部实例化
private Singleton() {}
// 公共的静态方法,用于获取单例实例
public static Singleton getInstance() {
return INSTANCE;
}
}
优点:
- 实现简单,类加载时就完成了实例化,避免了线程同步问题。
- 线程安全,因为实例是在类加载时就创建的,不存在多线程环境下创建多个实例的问题。
缺点:
- 类加载时就创建实例,可能会造成资源浪费,尤其是当实例的创建比较耗时或者占用大量资源,而在程序运行过程中可能并不会马上使用该实例时。
2. 饿汉式(静态代码块)
public class Singleton {
private static Singleton INSTANCE;
static {
// 在静态代码块中创建实例
INSTANCE = new Singleton();
}
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
}
优点:
- 同样实现简单,线程安全,因为静态代码块在类加载时执行,且只会执行一次。
缺点:
- 与静态常量方式一样,类加载时就创建实例,可能会造成资源浪费。
3. 懒汉式(线程不安全)
public class Singleton {
private static Singleton INSTANCE;
private Singleton() {}
public static Singleton getInstance() {
if (INSTANCE == null) {
// 当实例为 null 时创建实例
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
优点:
- 实现了懒加载,即只有在需要使用实例时才会创建,避免了不必要的资源浪费。
缺点:
- 线程不安全,在多线程环境下,如果多个线程同时进入
if (INSTANCE == null)
语句块,可能会创建多个实例,违背了单例模式的原则。
4. 懒汉式(线程安全,同步方法)
public class Singleton {
private static Singleton INSTANCE;
private Singleton() {}
// 使用 synchronized 关键字保证线程安全
public static synchronized Singleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
优点:
- 实现了懒加载,并且保证了线程安全。
缺点:
- 由于使用了
synchronized
关键字修饰方法,每次调用getInstance()
方法都会进行同步,会造成不必要的同步开销,影响性能。
5. 双重检查锁定(DCL,Double-Checked Locking)
public class Singleton {
// 使用 volatile 关键字保证可见性
private static volatile Singleton INSTANCE;
private Singleton() {}
public static Singleton getInstance() {
if (INSTANCE == null) {
synchronized (Singleton.class) {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
优点:
- 实现了懒加载,同时保证了线程安全。
- 只有在第一次创建实例时才会进行同步,避免了每次调用
getInstance()
方法都进行同步,提高了性能。
缺点:
- 实现相对复杂,需要使用
volatile
关键字来保证可见性,避免指令重排序导致的问题。
6. 静态内部类
public class Singleton {
private Singleton() {}
// 静态内部类,在类加载时不会创建实例
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
// 调用静态内部类的静态常量获取实例
return SingletonHolder.INSTANCE;
}
}
优点:
- 实现了懒加载,因为静态内部类在被调用时才会加载,加载时才会创建实例。
- 线程安全,因为类的静态属性只会在第一次加载类时初始化,JVM 保证了线程的安全性。
- 实现简单,不需要额外的同步和复杂的逻辑。
缺点:
- 无法传递参数,如果单例对象需要接收参数进行初始化,这种方式不太适用。
7. 枚举
public enum Singleton {
INSTANCE;
public void doSomething() {
System.out.println("Doing something...");
}
}
优点:
- 线程安全,枚举类型是由 JVM 保证线程安全的。
- 可以防止反序列化重新创建新的对象,因为枚举类型在序列化和反序列化时会保证实例的唯一性。
- 实现简单,代码量少。
缺点:
- 灵活性较差,枚举类型的实例创建是在类加载时完成的,无法实现懒加载。
综上所述,在选择单例模式的实现方式时,需要根据具体的需求和场景来决定。如果需要懒加载和高性能,推荐使用双重检查锁定或静态内部类方式;如果对反序列化和线程安全有严格要求,枚举方式是一个不错的选择。