文章目录
什么是单例模式?
保证一个类在全局只有一个实例,并提供一个全局访问点。
适用场景:配置类、连接池、日志对象等全局唯一的资源。
实现方式
饿汉式
类加载时就创建实例,天然线程安全。
java
class HungrySingleton {
private static final HungrySingleton instance = new HungrySingleton();
private HungrySingleton() {}
public static HungrySingleton getInstance() {
return instance;
}
}
优点:简单,线程安全
缺点:不管用不用都会创建对象,可能浪费内存
懒汉式
方式一(线程不安全)
java
class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {}
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
延迟创建对象,但多线程下可能创建多个实例,线程不安全。
方式二(同步方法)
java
class SafeThreadLazySingleton {
private static SafeThreadLazySingleton instance;
private SafeThreadLazySingleton() {}
public static synchronized SafeThreadLazySingleton getInstance() {
if (instance == null) {
instance = new SafeThreadLazySingleton();
}
return instance;
}
}
加锁保证线程安全,但每次获取对象都要经过锁竞争,性能不好。
方式三(双重检查锁 DCL)
java
class DCLLazySingleton {
private static volatile DCLLazySingleton instance;
private DCLLazySingleton() {}
public static DCLLazySingleton getInstance() {
if (instance == null) {
synchronized (DCLLazySingleton.class) {
if (instance == null) {
instance = new DCLLazySingleton();
}
}
}
return instance;
}
}
第一个 if:判断对象是否已经创建,避免每次都进入同步块,提升性能。
第二个 if:高并发下第一个 if 可能同时放进来多个线程(如 A、B、C),它们被锁阻塞。A进入临界区创建了对象后退出,B 或 C 再进来时,第二个 if 告诉它对象已存在,直接返回。
volatile 的作用:防止指令重排序。new 一个对象分三步:① 分配内存 → ② 初始化对象 → ③将引用指向内存地址。JVM 可能将顺序优化为①③②,此时另一个线程拿到的是未初始化完成的对象,产生空指针问题。volatile禁止这种重排序,保证对象完整创建后才对外可见。
枚举
java
enum EnumSingleton {
INSTANCE;
public void doSomething() {
// 业务方法
}
}
java
// 使用
EnumSingleton.INSTANCE.doSomething();
简洁优雅,属于饿汉式。是唯一不会被反射和反序列化破坏的单例实现,Josh Bloch 在《Effective Java》中推荐的方式。
除枚举外,其他单例都可以通过以下方式强行创建新实例:
- 反射:Constructor.setAccessible(true) 绕过私有构造器
- 反序列化:对象实现 Serializable 后,反序列化会创建新对象
枚举在 JVM 层面做了保护,这两种方式对它均无效。