面试题答案
一键面试饿汉式
- 性能:类加载时就初始化实例,可能会导致加载时间变长,若该单例对象占用资源多,会影响启动性能。但后续获取实例时速度快,因为无需判断是否初始化。
- 线程安全:线程安全,因为在类加载阶段由 JVM 保证只创建一个实例。
懒汉式
- 性能:延迟初始化,首次使用时才创建实例,在实例创建前性能较好。但每次获取实例都要进行同步检查,在多线程环境下频繁同步操作会带来性能开销。
- 线程安全:如果不进行同步控制,是非线程安全的。若加上
synchronized
关键字修饰获取实例方法,可保证线程安全,但同步带来性能损耗。
双重检查锁
- 性能:兼顾了懒加载和高性能。在第一次检查
instance == null
时,多数情况下不需要进入同步块,减少了同步开销,提升了性能。只有在第一次实例化时才会进入同步块,后续获取实例速度快。 - 线程安全:在 JDK 1.5 之后,通过
volatile
关键字修饰实例变量,确保了指令重排序不会导致获取到未完全初始化的实例,保证了线程安全。
静态内部类
- 性能:同样具有懒加载特性,在外部类加载时,静态内部类不会被加载,只有在调用获取实例方法时,静态内部类才会加载并初始化实例。由于类加载由 JVM 保证线程安全,且仅执行一次,后续获取实例性能好。
- 线程安全:线程安全,由 JVM 类加载机制保证,静态内部类只会被加载一次,从而保证单例实例唯一。
枚举
- 性能:在枚举类加载时就创建实例,与饿汉式类似,后续获取实例性能好。
- 线程安全:线程安全,JVM 保证枚举实例的唯一性和线程安全性,同时还防止反序列化重新创建新的实例。
高并发场景选择及原因
在高并发场景下,推荐使用枚举或双重检查锁方式。
枚举:实现简单,由 JVM 保证线程安全和实例唯一性,并且天然防止反序列化创建新实例,性能上类加载后获取实例速度快,适用于大多数高并发场景。
双重检查锁:如果希望实现懒加载且对性能有较高要求,双重检查锁方式是不错的选择。它通过减少不必要的同步操作,在保证线程安全的同时,提升了获取实例的性能。不过代码实现相对复杂,需要注意volatile
关键字的使用。