单例模式是一种创建型设计模式,确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。在 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 保证线程安全的。
  • 可以防止反序列化重新创建新的对象,因为枚举类型在序列化和反序列化时会保证实例的唯一性。
  • 实现简单,代码量少。

缺点

  • 灵活性较差,枚举类型的实例创建是在类加载时完成的,无法实现懒加载。

综上所述,在选择单例模式的实现方式时,需要根据具体的需求和场景来决定。如果需要懒加载和高性能,推荐使用双重检查锁定或静态内部类方式;如果对反序列化和线程安全有严格要求,枚举方式是一个不错的选择。