重学 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 单例模式的实现及其优缺点。在实际应用中,我们需要根据具体场景灵活地选择不同的单例模式,结合业务特点来设计实现。希望本文对您有所帮助,谢谢!