面试题答案
一键面试单例模式在高并发环境下不同实现方式的性能特点及线程安全问题
- 饿汉式单例
- 实现方式:在类加载时就创建单例实例。
public class EagerSingleton { private static final EagerSingleton instance = new EagerSingleton(); private EagerSingleton() {} public static EagerSingleton getInstance() { return instance; } }
- 性能特点:由于在类加载时就创建实例,不存在并发创建的性能开销,调用
getInstance
方法时直接返回实例,速度快。 - 线程安全:线程安全,因为类加载机制保证了实例只会被创建一次。
- 懒汉式单例(非线程安全)
- 实现方式:在第一次调用
getInstance
方法时才创建实例。
public class LazySingleton { private static LazySingleton instance; private LazySingleton() {} public static LazySingleton getInstance() { if (instance == null) { instance = new LazySingleton(); } return instance; } }
- 性能特点:延迟初始化,在实例未创建时,调用
getInstance
方法性能较好。但当多线程并发调用getInstance
且实例未创建时,可能会创建多个实例。 - 线程安全:非线程安全,多线程环境下可能出现多个实例。
- 实现方式:在第一次调用
- 懒汉式单例(线程安全,同步方法)
- 实现方式:通过在
getInstance
方法上加synchronized
关键字保证线程安全。
public class LazySafeSingleton { private static LazySafeSingleton instance; private LazySafeSingleton() {} public static synchronized LazySafeSingleton getInstance() { if (instance == null) { instance = new LazySafeSingleton(); } return instance; } }
- 性能特点:由于
synchronized
关键字,每次调用getInstance
方法都会进行同步操作,性能开销较大,尤其是在高并发场景下。 - 线程安全:线程安全,保证了只有一个实例被创建。
- 实现方式:通过在
- 双重检查锁定(DCL)单例
- 实现方式:通过两次检查
instance
是否为null
并使用synchronized
块来保证线程安全和性能。
public class DoubleCheckLockSingleton { private volatile static DoubleCheckLockSingleton instance; private DoubleCheckLockSingleton() {} public static DoubleCheckLockSingleton getInstance() { if (instance == null) { synchronized (DoubleCheckLockSingleton.class) { if (instance == null) { instance = new DoubleCheckLockSingleton(); } } } return instance; } }
- 性能特点:第一次
if (instance == null)
检查避免了不必要的同步操作,只有在实例未创建时才进入同步块,性能相对较好。volatile
关键字保证了实例的可见性和禁止指令重排序。 - 线程安全:线程安全,通过双重检查和
volatile
关键字确保了在多线程环境下只有一个实例被创建。
- 实现方式:通过两次检查
- 静态内部类单例
- 实现方式:利用静态内部类的特性实现延迟初始化。
public class StaticInnerClassSingleton { private StaticInnerClassSingleton() {} private static class SingletonHolder { private static final StaticInnerClassSingleton instance = new StaticInnerClassSingleton(); } public static StaticInnerClassSingleton getInstance() { return SingletonHolder.instance; } }
- 性能特点:延迟初始化,在调用
getInstance
方法时才加载静态内部类并创建实例。同时,由于类加载机制保证了线程安全,性能较好。 - 线程安全:线程安全,借助类加载机制保证了实例只会被创建一次。
- 枚举单例
- 实现方式:使用枚举类型实现单例。
public enum EnumSingleton { INSTANCE; // 可以添加其他方法和属性 }
- 性能特点:实现简单,线程安全,且在反序列化时也能保证单例。由于枚举类型在类加载时就被初始化,调用
INSTANCE
性能较好。 - 线程安全:线程安全,枚举类型本身保证了实例的唯一性。
如何权衡使用不同的单例实现来满足性能需求
- 性能要求不高且线程安全要求低:可以使用懒汉式单例(非线程安全),适用于单线程环境或对线程安全要求不严格的场景。
- 对线程安全要求高但性能要求不极致:懒汉式单例(同步方法)可以满足需求,但在高并发场景下性能较差。
- 高并发且对性能要求较高:双重检查锁定(DCL)单例、静态内部类单例或枚举单例是较好的选择。DCL 单例通过双重检查和
volatile
关键字在保证线程安全的同时提升性能;静态内部类单例利用类加载机制实现延迟初始化和线程安全;枚举单例实现简单且线程安全,还能防止反序列化破坏单例。
在何种情况下需要对设计模式进行性能优化改造及改造思路
- 情况:当设计模式在高并发场景下出现性能瓶颈,如频繁的同步操作导致大量线程等待,或者实例创建开销过大影响系统响应速度时,需要进行性能优化改造。
- 改造思路
- 减少同步范围:例如从方法级同步改为块级同步,像 DCL 单例那样,只在必要时进行同步操作,减少线程等待时间。
- 延迟初始化:采用静态内部类单例或懒汉式单例的优化方式,避免在系统启动时就创建大量不必要的实例,减少资源消耗。
- 缓存复用:对于一些创建开销大的对象,可以在单例中缓存已创建的对象并复用,减少创建次数。
- 使用更高效的数据结构或算法:根据具体业务场景,优化单例内部的数据结构和算法,提高操作效率。