Java 设计模式:单例模式的 10 种实现方式及优缺点

Java 设计模式:单例模式的 10 种实现方式及优缺点

单例模式(Singleton Pattern)是设计模式中最为经典的模式之一,其核心思想是确保一个类只有一个实例,并提供一个全局访问点。单例模式在实际开发中非常常见,比如配置管理器、日志记录器、线程池等场景。本文将详细探讨 Java 中单例模式的 10 种实现方式,并分析每种方式的优缺点。

单例模式简介

单例模式的实现需要满足以下三个条件:

  1. 私有化构造方法:防止外部通过 new 关键字创建实例。
  2. 提供一个静态的私有实例:确保类只有一个实例。
  3. 提供一个公共的静态方法:用于获取唯一的实例。

单例模式可以分为“懒汉式”和“饿汉式”两大类,其中懒汉式在需要时才创建实例,而饿汉式在类加载时就创建实例。此外,还有一些更高级的实现方式,比如使用枚举、静态内部类等。

懒汉式单例(非线程安全)

懒汉式单例是最简单的实现方式,它在第一次调用 getInstance() 方法时才创建实例。这种方式的优点是延迟加载,但缺点是线程不安全。

public class LazySingleton {
    private static LazySingleton instance;

    private LazySingleton() {}

    public static LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

优点

  • 延迟加载,节省资源。

缺点

  • 在多线程环境下,可能会创建多个实例。

懒汉式单例(线程安全)

为了保证线程安全,可以在 getInstance() 方法上加 synchronized 关键字。

public class ThreadSafeLazySingleton {
    private static ThreadSafeLazySingleton instance;

    private ThreadSafeLazySingleton() {}

    public static synchronized ThreadSafeLazySingleton getInstance() {
        if (instance == null) {
            instance = new ThreadSafeLazySingleton();
        }
        return instance;
    }
}

优点

  • 线程安全。

缺点

  • 每次调用 getInstance() 都需要加锁,性能较差。

双重检查锁定(Double-Checked Locking)

双重检查锁定是一种优化的懒汉式实现方式,它通过两次检查实例是否为 null 来减少锁的使用,从而提高性能。

public class DoubleCheckedSingleton {
    private static volatile DoubleCheckedSingleton instance;

    private DoubleCheckedSingleton() {}

    public static DoubleCheckedSingleton getInstance() {
        if (instance == null) {
            synchronized (DoubleCheckedSingleton.class) {
                if (instance == null) {
                    instance = new DoubleCheckedSingleton();
                }
            }
        }
        return instance;
    }
}

优点

  • 线程安全,且只在第一次创建实例时加锁,性能较高。

缺点

  • 实现稍微复杂,需要理解 volatile 的作用。

饿汉式单例

饿汉式单例在类加载时就创建实例,因此是线程安全的,但无法实现延迟加载。

public class EagerSingleton {
    private static final EagerSingleton instance = new EagerSingleton();

    private EagerSingleton() {}

    public static EagerSingleton getInstance() {
        return instance;
    }
}

优点

  • 线程安全,实现简单。

缺点

  • 无法延迟加载,可能导致资源浪费。

静态内部类实现

静态内部类是一种优雅的单例实现方式,它利用 Java 的类加载机制来保证线程安全,同时实现延迟加载。

public class StaticInnerClassSingleton {
    private static class Holder {
        private static final StaticInnerClassSingleton instance = new StaticInnerClassSingleton();
    }

    private StaticInnerClassSingleton() {}

    public static StaticInnerClassSingleton getInstance() {
        return Holder.instance;
    }
}

优点

  • 线程安全,延迟加载,实现简单。

缺点

  • 需要理解静态内部类的加载机制。

枚举实现单例

枚举是 Java 中实现单例的一种推荐方式,它天然支持线程安全,并且可以防止反射攻击。

public enum EnumSingleton {
    INSTANCE;

    public void doSomething() {
        System.out.println("EnumSingleton is working");
    }
}

优点

  • 线程安全,防止反射攻击,实现简单。

缺点

  • 不支持延迟加载,且枚举的使用场景有限。

容器式单例

容器式单例适用于管理多个单例对象的场景,通常使用 Map 来存储实例。

public class ContainerSingleton {
    private static Map<String, Object> instances = new ConcurrentHashMap<>();

    private ContainerSingleton() {}

    public static Object getInstance(String className) {
        if (!instances.containsKey(className)) {
            synchronized (ContainerSingleton.class) {
                if (!instances.containsKey(className)) {
                    try {
                        instances.put(className, Class.forName(className).newInstance());
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        return instances.get(className);
    }
}

优点

  • 可以管理多个单例对象。

缺点

  • 实现较为复杂。

反射攻击的防御

为了防止反射破坏单例模式,可以在构造方法中抛出异常。

public class ReflectionSafeSingleton {
    private static final ReflectionSafeSingleton instance = new ReflectionSafeSingleton();

    static {
        // 防止反射攻击
        ReflectionSafeSingleton.class.getDeclaredConstructors()[0].setAccessible(false);
    }

    private ReflectionSafeSingleton() {
        if (instance != null) {
            throw new RuntimeException("Singleton instance already exists");
        }
    }

    public static ReflectionSafeSingleton getInstance() {
        return instance;
    }
}

优点

  • 防止反射攻击。

缺点

  • 实现较为复杂。

序列化与反序列化安全

为了防止序列化和反序列化破坏单例,可以重写 readResolve 方法。

public class SerializableSingleton implements Serializable {
    private static final long serialVersionUID = 1L;
    private static final SerializableSingleton instance = new SerializableSingleton();

    private SerializableSingleton() {}

    public static SerializableSingleton getInstance() {
        return instance;
    }

    // 防止反序列化创建新实例
    protected Object readResolve() {
        return instance;
    }
}

优点

  • 防止序列化和反序列化破坏单例。

缺点

  • 需要实现 Serializable 接口。

总结

单例模式的实现方式多种多样,每种方式都有其适用场景和优缺点。在实际开发中,可以根据需求选择合适的实现方式:

  • 简单场景:推荐使用静态内部类或枚举实现。
  • 需要延迟加载:使用双重检查锁定。
  • 需要管理多个单例:使用容器式单例。
  • 需要防止反射攻击:使用反射安全的单例实现。
  • 需要序列化安全:使用序列化安全的单例实现。

理解单例模式的多种实现方式及其优缺点,可以帮助我们更好地应对实际开发中的各种需求。

在这里插入图片描述

你可能感兴趣的:(Java入门到精通,java,设计模式,单例模式)