1.单例模式介绍
1.1 简介
单例(Singleton)模式是一种创建型设计模式,旨在确保一个类只有一个实例,并提供一个全局访问点来获取该实例。 这在需要对某个资源进行集中管理或限制其实例化时非常有用,比如日志记录器、配置管理器或者连接池。
可以将Singleton模式类比为一个总统职位。 在一个国家里,通常只能有一个总统,这个职位的独特性意味着不管你在哪里提到总统,都指的是同一个人。 这个职位的管理需要集中化,这样才能确保权力和决策的一致性。
1.1 实现要点:
- 私有构造函数:阻止外部类直接实例化。
- 静态实例:在类内部创建一个静态实例。
- 公共静态方法:提供一个公共的静态方法来获取唯一的实例。
1.2 使用演示
step1. 新建单例类:
/**
* 实现要点
* - 私有构造函数:阻止外部类直接实例化。
* - 静态实例:在类内部创建一个静态实例。
* - 公共静态方法:提供一个公共的静态方法来获取唯一的实例。
*/
public class Singleton {
/**
* 私有静态实例,确保唯一性
*/
private static Singleton instance;
/**
* 私有构造函数,防止外部实例化
*/
private Singleton() {}
/**
* 公共静态方法,提供全局访问点
*
* @return Singleton
*/
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
/**
* 示例方法
*/
public void showMessage() {
System.out.println("Hello, I am a Singleton!");
}
}
step2. 调用单例方法
public class SingletonDemo {
public static void main(String[] args) {
// 获取Singleton实例
Singleton singleton = Singleton.getInstance();
// 调用实例方法
singleton.showMessage();
}
}
2.单例实现方式
2.1 饿汉式
代码:
public class HungryStyleIdGenerator {
/**
* 在类加载的时候,instance静态实例就已经创建并初始化好了
*/
private static final HungryStyleIdGenerator INSTANCE = new HungryStyleIdGenerator();
private final AtomicLong atomicLong = new AtomicLong(0);
private HungryStyleIdGenerator() {}
public static HungryStyleIdGenerator getInstance() {
return INSTANCE;
}
public long getId() {
return atomicLong.incrementAndGet();
}
}
优点:
- 实现最简单
- 线程绝对安全
- 没有并发性能损耗
缺点:
- 非延迟加载(类加载时立即初始化)
- 可能造成资源浪费
适用场景:
- 实例较小且使用频繁
- 对内存敏感的系统中慎用
2.2 懒汉式
代码:
public class LazyStyleIdGenerator {
private static LazyStyleIdGenerator INSTANCE;
private final AtomicLong atomicLong = new AtomicLong(0);
private LazyStyleIdGenerator() {}
/**
* 方法加了锁,导致这个函数的并发度很低(并发度为1)
*/
public static synchronized LazyStyleIdGenerator getInstance() {
if (INSTANCE == null) {
INSTANCE = new LazyStyleIdGenerator();
}
return INSTANCE;
}
public long getId() {
return atomicLong.incrementAndGet();
}
}
优点:
- 实现简单
- 延迟加载(按需初始化)
- 线程安全
缺点:
- 并发度低
- 代码相对复杂
适用场景:
- 实例创建成本高且使用频率不确定时
- 需要严格控制资源分配的场景
2.3 双重检测
代码:
public class DoubleCheckStyleIdGenerator {
private static DoubleCheckStyleIdGenerator INSTANCE;
private final AtomicLong atomicLong = new AtomicLong(0);
private DoubleCheckStyleIdGenerator() {}
/**
* 只要instance被创建之后,即便再调用getInstance()函数也不会再进入到加锁逻辑中了。所以,这种实现方式解决了懒汉式并发度低的问题。
*/
public static DoubleCheckStyleIdGenerator getInstance() {
if (INSTANCE == null) {
synchronized (DoubleCheckStyleIdGenerator.class) {
if (INSTANCE == null) {
INSTANCE = new DoubleCheckStyleIdGenerator();
}
}
}
return INSTANCE;
}
public long getId() {
return atomicLong.incrementAndGet();
}
}
优点:
- 延迟加载
- 高并发性能好
- 内存占用优化
缺点:
- 实现复杂
- 可读性较差
适用场景:
- 高并发系统中需要延迟加载
- 对性能要求极高的场景
2.4 静态内部类
代码:
/**
* instance的唯一性、创建过程的线程安全性,都由JVM来保证。
*
* Java的类加载机制是线程安全的,具体由JVM保证:
* - 当多个线程同时尝试访问一个未加载的类时,JVM会确保只由一个线程触发类的初始化,其他线程会等待。
* - 类的初始化(包括静态代码块和静态变量赋值)是同步的,通过Class对象的锁(monitor)实现。
*/
public class StaticInnerClassStyleIdGenerator {
private final AtomicLong atomicLong = new AtomicLong(0);
private StaticInnerClassStyleIdGenerator() {}
/**
* 静态内部类,当外部类 StaticInnerClassStyleIdGenerator 被加载的时候,并不会创建SingletonHolder实例对象。
* 只有当调用getInstance()方法时,SingletonHolder才会被加载,这个时候才会创建instance。
*/
private static class SingletonHolder {
private static final StaticInnerClassStyleIdGenerator INSTANCE = new StaticInnerClassStyleIdGenerator();
}
public static StaticInnerClassStyleIdGenerator getInstance() {
return SingletonHolder.INSTANCE;
}
public long getId() {
return atomicLong.incrementAndGet();
}
}
优点:
- 实现简单优雅
- 延迟加载
- 线程安全
- 不需要额外同步代码
缺点:
- JVM类加载机制依赖
适用场景:
- 推荐作为默认实现方式
- 适用于大多数单例需求场景
2.5 枚举
代码:
public enum EnumStyleIdGenerator {
/**
* 通过Java枚举类型本身的特性,保证了实例创建的线程安全性和实例的唯一性
*/
INSTANCE;
private final AtomicLong atomicLong = new AtomicLong(0);
public long getId() {
return atomicLong.incrementAndGet();
}
}
优点:
- 简洁优雅
- 天然线程安全
- 防止反序列化破坏
- 防止反射攻击
- 支持序列化
缺点:
- 不支持延迟初始化
- 不符合传统单例模式结构
适用场景:
- 需要绝对线程安全的场景
- 需要序列化的单例对象
- 项目规范允许使用枚举时
2.6 单例的5种实现方式对比
实现方式 | 延迟加载 | 线程安全 | 反射破坏 | 序列化安全 | 性能 | 推荐度 |
---|---|---|---|---|---|---|
饿汉式 | ❌ | ✅ | ❌ | ❌ | 最高 | ★★★☆ |
懒汉式 | ✅ | ✅ | ❌ | ❌ | 中等 | ★★☆ |
双重检查(DCL) | ✅ | ✅ | ❌ | ❌ | 高 | ★★★☆ |
静态内部类 | ✅ | ✅ | ❌ | ❌ | 高 | ★★★★☆ |
枚举 | ❌ | ✅ | ✅ | ✅ | 高 | ★★★★ |
使用建议:
- 优先选择:静态内部类实现(兼顾延迟加载和线程安全)
- 绝对安全:枚举方式(推荐用于金融、安全等关键系统)
- 特殊需求:
- 需要延迟初始化 + 高并发 → 双重检测DCL
- 实例小且高频使用 → 饿汉式
- 传统项目 → 懒汉式(注意同步)