重学 Java 设计模式:实战单例模式
Java 设计模式中的单例模式旨在确保某个类在整个项目中只有一个实例,并且提供一个全局访问点,方便我们在其他类中调用。
本文将通过实战的方式,介绍七种常见的单例模式,并详细阐述其特点、优点和适用场景。最后我们将介绍 Effective Java 作者推荐的枚举单例模式,并介绍其优点和实现方式。
饿汉式单例模式
饿汉式单例模式的实现方式非常简单,即在类加载时就创建好对象实例,这样可以确保在多线程下也能够保持实例唯一性。以下是其实现代码:
public class SingletonOne { // 在类加载时就创建好对象实例 private static final SingletonOne INSTANCE = new SingletonOne(); // 构造方法私有化,防止其他类创建实例 private SingletonOne() {} // 提供全局访问点,返回唯一实例 public static SingletonOne getInstance() { return INSTANCE; } }
饿汉式单例模式的优点是实现简单、线程安全,不存在多线程下的单例对象创建问题,适用于单线程或者初始化时间比较短的情况。但是缺点也很明显,就是会在类加载时进行实例化。如果这个单例比较复杂、需要一定的耗时才能完成初始化,那么程序启动的时间将会很长。
懒汉式单例模式
懒汉式单例模式是在调用获取实例方法时才进行对象的实例化,它实现了懒加载的效果,节省了程序启动时间。但是,在多线程下会出现单例对象创建的问题,需要加上同步锁来保证线程安全。以下是懒汉式单例模式的实现代码:
public class SingletonTwo { // 需要在调用获取实例方法时才创建实例 private static SingletonTwo INSTANCE = null; // 构造方法私有化,防止其他类创建实例 private SingletonTwo() {} // 提供全局访问点,返回唯一实例 public static synchronized SingletonTwo getInstance() { if (INSTANCE == null) { INSTANCE = new SingletonTwo(); } return INSTANCE; } }
懒汉式单例模式的优点是实现了懒加载,节省了程序启动时间。缺点是需要加上同步锁来保证线程安全,每次获取实例时都需要进行同步,如果在高并发场景下,会降低系统性能。
双重检查锁单例模式
双重检查锁单例模式通过双重检查锁和 volatile
关键字来确保线程安全。在进行 instance
实例化时,通过两次判断加锁的方式来确保只有一个实例。以下是双重检查锁单例模式的实现代码:
public class SingletonThree { // 需要在调用获取实例方法时才创建实例 private static volatile SingletonThree INSTANCE = null; // 构造方法私有化,防止其他类创建实例 private SingletonThree() {} // 提供全局访问点,返回唯一实例 public static SingletonThree getInstance() { if (INSTANCE == null) { synchronized (SingletonThree.class) { if (INSTANCE == null) { INSTANCE = new SingletonThree(); } } } return INSTANCE; } }
双重检查锁单例模式的优点是通过双重检查锁和 volatile 关键字来确保线程安全,兼顾了懒加载和线程安全两个特点。但是由于需要进行双重检查锁,因此会降低系统性能,在单例比较简单的情况下,建议使用懒汉式单例模式。
静态内部类单例模式
静态内部类单例模式的实现方式结合了懒加载和线程安全的特点,在调用 getInstance
方法时才实例化对象,同时利用 JDK 特性保证了线程安全。以下是静态内部类单例模式的实现代码:
public class SingletonFour { // 静态内部类只会在第一次被使用的时候加载 private static class SingletonHolder { private static final SingletonFour INSTANCE = new SingletonFour(); } // 构造方法私有化,防止其他类创建实例 private SingletonFour() {} // 提供全局访问点,返回唯一实例 public static SingletonFour getInstance() { return SingletonHolder.INSTANCE; } }
静态内部类单例模式的优点是实现了懒加载和线程安全,兼顾了多种情况下的需求。缺点是使用了静态内部类,增加了代码的复杂度。
枚举单例模式
Effective Java 作者推荐的枚举单例模式是实现单例模式的最佳方式。Java 枚举天生具有单例模式的特征,因此只需要将构造方法私有化,并将其设置为枚举类型即可实现单例模式。以下是枚举单例模式的实现代码:
public enum SingletonFive { INSTANCE; // 构造方法只会在枚举类型初始化时执行一次 private SingletonFive() {} // 提供全局访问点,返回唯一实例 public static SingletonFive getInstance() { return INSTANCE; } }
枚举单例模式的优点是枚举类天生单例模式,线程安全、防止反序列化重新创建新的对象,同时实现简单、代码量少。缺点是不支持懒加载和延迟加载,使用枚举类实现的单例模式不能被继承。
ThreadLocal 单例模式
ThreadLocal
单例模式维护了一个线程变量,在每个线程中都拥有一个单独的实例。通过 ThreadLocal
保证了线程安全,并且实现了懒加载的效果。以下是 ThreadLocal
单例模式的实现代码:
public class SingletonSix { // 通过 ThreadLocal 维护了一个线程变量,并在需要时才实例化对象 private static ThreadLocal<SingletonSix> THREAD_LOCAL = ThreadLocal.withInitial(SingletonSix::new); // 构造方法私有化,防止其他类创建实例 private SingletonSix() {} // 提供全局访问点,返回唯一实例 public static SingletonSix getInstance() { return THREAD_LOCAL.get(); } }
ThreadLocal
单例模式的优点是实现了懒加载、线程安全,并且在多线程下也能运行良好。缺点是可能在某些情况下导致资源占用过多,在实际使用过程中需要加以注意。
容器单例模式
容器单例模式又叫注册式单例模式,将每个实例都注册到一个容器中,再需要使用时从容器中获取实例对象。使用容器单例模式可以动态地注册和获取实例对象,同时也可以保证实例的唯一性。以下是容器单例模式的实现代码:
public class SingletonContainer { // 使用 ConcurrentHashMap 保证线程安全 private static Map<String, Object> singletonMap = new ConcurrentHashMap<>(); private SingletonContainer() {} // 向容器中注册实例对象 public static void registerInstance(String key, Object instance) { if (!singletonMap.containsKey(key)) { singletonMap.put(key, instance); } } // 从容器中获取实例对象 public static Object getInstance(String key) { return singletonMap.get(key); } }
容器单例模式的优点是实现了动态注册和获取实例对象,使用时可以直接从容器中取值,可保证实例对象唯一性。缺点是需要使用 ConcurrentHashMap
等线程安全的容器,同时容器单例模式的实例对象是存储在容器中的,需要耗费一定的内存空间。
综上所述,通过以上七种实战方式,我们详细介绍了 Java 单例模式的实现及其优缺点。在实际应用中,我们需要根据具体场景灵活地选择不同的单例模式,结合业务特点来设计实现。希望本文对您有所帮助,谢谢!