# Java单例模式实现方法大全:从基础到最佳实践
## 一、单例模式的定义
### 1.1 什么是单例模式?
**单例模式就是一个类在任何情况下绝对只有一个实例,并且提供一个全局访问点来获取这个实例。**
### 1.2 单例模式的四个要点
**要实现单例,至少需要满足四个点:**
1. **第一,私有化构造方法**:去防止被外部实例化,造成多实例问题
2. **第二,提供一个静态的方法**:作为全局访问点来获取唯一的实例对象
3. **第三,确保线程安全**:在多线程环境下保证只有一个实例
4. **第四,防止反序列化、反射等破坏单例**:保证单例的绝对安全
## 二、Java中实现单例的六种方法
**在Java里面至少有六种方法来实现单例。**
### 2.1 方法一:懒汉式(线程不安全)
**第一种,通过延迟加载的方式来实现实例化,并且增加同步锁的机制来避免多线程环境下的线程安全问题。**
**基础版本(线程不安全):**
```java
public class Singleton {
private static Singleton instance;
// 私有化构造方法
private Singleton() {}
// 全局访问点
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
```
**问题:**
- **线程不安全**:多线程环境下可能创建多个实例
- **不推荐使用**
### 2.2 方法二:懒汉式(同步方法)
**但是这种加锁会造成性能问题。**
**同步方法版本:**
```java
public class Singleton {
private static Singleton instance;
private Singleton() {}
// 同步方法,保证线程安全
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
```
**优点:**
- 线程安全
- 延迟加载
**缺点:**
- **性能问题**:每次获取实例都要加锁,性能较差
- **锁粒度大**:整个方法都加锁
### 2.3 方法三:双重检查锁定(DCL)
**而且同步锁只有在第一次实例化的时候才会产生作用,后续不需要。**
**于是就有了第二种改进方案,通过双重检查锁定的方式,减少了锁的范围来提升性能。**
**双重检查锁定版本:**
```java
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
// 第一次检查,避免不必要的加锁
if (instance == null) {
synchronized (Singleton.class) {
// 第二次检查,确保只创建一个实例
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
```
**关键点:**
- **volatile关键字**:防止指令重排序,保证可见性
- **双重检查**:减少锁的竞争,提升性能
- **延迟加载**:只有在第一次使用时才创建实例
**优点:**
- 线程安全
- 延迟加载
- 性能较好(只在第一次加锁)
**缺点:**
- 代码相对复杂
- 需要理解volatile的作用
### 2.4 方法四:饿汉式
**第三种,通过饿汉式实现单例。**
**这种方式在类加载的时候就触发实例化,从而避免了多线程同步问题。**
**饿汉式版本:**
```java
public class Singleton {
// 类加载时就创建实例
private static final Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
```
**优点:**
- 线程安全(类加载时创建,JVM保证线程安全)
- 实现简单
- 性能好(无锁)
**缺点:**
- **不是延迟加载**:类加载时就创建,可能浪费资源
- 如果实例创建耗时,会影响类加载速度
### 2.5 方法五:静态代码块
**还有一种与这个方法类似的实现,通过在静态代码块里面实例化,而静态代码块是在类加载的时候触发执行的。所以它也只会执行一次。**
**静态代码块版本:**
```java
public class Singleton {
private static Singleton instance;
// 静态代码块,类加载时执行
static {
instance = new Singleton();
}
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
```
**特点:**
- 与饿汉式类似
- 类加载时创建实例
- 线程安全
- 不是延迟加载
### 2.6 方法六:静态内部类
**以上两种方法都是在类加载的时候实例化,没有达到延迟加载的效果。当然本身影响不大,但是还是可以进一步的优化,就是可以在使用的时候去触发实例化。**
**像这种写法,把Instance实例写在一个静态内部类里面。**
**由于静态内部类只有调用静态内部类的方法、静态域或者构造方法的时候才会加载静态内部类。**
**所以当这个类被加载的时候不会实例化Instance实例,从而就实现了延迟加载。**
**静态内部类版本:**
```java
public class Singleton {
private Singleton() {}
// 静态内部类
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
// 第一次调用时才加载静态内部类,创建实例
return SingletonHolder.INSTANCE;
}
}
```
**优点:**
- **延迟加载**:只有在第一次调用getInstance()时才创建实例
- **线程安全**:JVM保证类加载的线程安全
- **性能好**:无锁,性能优秀
- **实现优雅**:代码简洁
**缺点:**
- 可能被反射破坏(可以通过其他方式防止)
### 2.7 方法七:枚举类
**另外我们还可以使用枚举类来实现。**
**这种写法呢,既能去避开多线程的同步问题,又能防止反射的话重新创建新对象,也是一个比较好的方案。**
**枚举类版本:**
```java
public enum Singleton {
INSTANCE;
// 可以添加方法
public void doSomething() {
// 业务方法
}
}
// 使用方式
Singleton instance = Singleton.INSTANCE;
```
**优点:**
- **线程安全**:枚举本身就是线程安全的
- **防止反射破坏**:枚举的构造方法是私有的,反射无法创建新实例
- **防止反序列化破坏**:枚举的反序列化不会创建新实例
- **防止克隆破坏**:枚举不支持clone方法
- **实现简单**:代码最简洁
**缺点:**
- 不是延迟加载(枚举在类加载时创建)
- 不够灵活(某些场景可能不适用)
## 三、单例模式的破坏和防护
### 3.1 单例可能被破坏的情况
**但是多线程、克隆、反序列化、反射都有可能会造成单例的破坏。**
**而我认为通过枚举的方式的实现单例是能够解决所有可能被破坏的情况。**
**破坏单例的方式:**
1. **反射破坏**:
```java
// 通过反射创建新实例
Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton instance2 = constructor.newInstance(); // 创建新实例
```
2. **反序列化破坏**:
```java
// 序列化和反序列化可能创建新实例
ObjectOutputStream oos = new ObjectOutputStream(...);
oos.writeObject(instance);
ObjectInputStream ois = new ObjectInputStream(...);
Singleton instance2 = (Singleton) ois.readObject(); // 可能创建新实例
```
3. **克隆破坏**:
```java
// 如果实现了Cloneable接口,可能通过克隆创建新实例
Singleton instance2 = (Singleton) instance.clone(); // 创建新实例
```
### 3.2 防护措施
**防止反射破坏:**
```java
public class Singleton {
private static boolean flag = false;
private Singleton() {
// 防止反射破坏
if (flag) {
throw new RuntimeException("不能通过反射创建实例");
}
flag = true;
}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
```
**防止反序列化破坏:**
```java
public class Singleton implements Serializable {
private static final long serialVersionUID = 1L;
private Singleton() {}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
// 防止反序列化破坏
private Object readResolve() {
return getInstance();
}
}
```
**防止克隆破坏:**
```java
public class Singleton implements Cloneable {
private Singleton() {}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
// 防止克隆破坏
@Override
protected Object clone() throws CloneNotSupportedException {
return getInstance(); // 返回同一个实例
// 或者抛出异常
// throw new CloneNotSupportedException();
}
}
```
## 四、三种推荐实现方式
### 4.1 方式一:双重检查锁定
**我认为有三种方式来去实现单例。**
**第一种是通过双重检查锁定的方式,它是一种线程安全并且延迟实例化的方式,但是因为加锁所以会有性能上的影响。**
**适用场景:**
- 需要延迟加载
- 对性能要求不是特别高
- 需要线程安全
### 4.2 方式二:静态内部类
**第二种是通过静态内部类的方式,它是一种延迟实例化,由于它是静态内部类,所以只会在使用的时候加载一次,不存在线程安全问题。**
**适用场景:**
- 需要延迟加载
- 对性能要求高
- 需要线程安全
- **推荐使用**
### 4.3 方式三:枚举类
**第三种是通过枚举类的方式实现,它既是线程安全的,又能防止反射的话导致的破坏单例问题。**
**适用场景:**
- 不需要延迟加载
- 需要绝对安全(防止反射、反序列化、克隆)
- **最推荐使用**
## 五、实现方式对比
### 5.1 对比表
| 实现方式 | 线程安全 | 延迟加载 | 性能 | 防止反射 | 防止反序列化 | 防止克隆 | 推荐度 |
|---------|---------|---------|------|---------|------------|---------|--------|
| **懒汉式(同步方法)** | ✅ | ✅ | 低 | ❌ | ❌ | ❌ | ⭐⭐ |
| **双重检查锁定** | ✅ | ✅ | 中 | ❌ | ❌ | ❌ | ⭐⭐⭐ |
| **饿汉式** | ✅ | ❌ | 高 | ❌ | ❌ | ❌ | ⭐⭐⭐ |
| **静态内部类** | ✅ | ✅ | 高 | ❌ | ❌ | ❌ | ⭐⭐⭐⭐ |
| **枚举类** | ✅ | ❌ | 高 | ✅ | ✅ | ✅ | ⭐⭐⭐⭐⭐ |
### 5.2 性能对比
**性能排序(从高到低):**
1. **枚举类**:无锁,性能最好
2. **静态内部类**:无锁,性能好
3. **饿汉式**:无锁,性能好
4. **双重检查锁定**:只在第一次加锁,性能中等
5. **懒汉式(同步方法)**:每次都要加锁,性能较差
### 5.3 安全性对比
**安全性排序(从高到低):**
1. **枚举类**:防止所有破坏方式
2. **静态内部类 + 防护措施**:可以防止大部分破坏
3. **双重检查锁定 + 防护措施**:可以防止大部分破坏
4. **其他方式**:容易被破坏
## 六、最佳实践
### 6.1 推荐方案
**根据实际需求选择:**
1. **一般场景**:使用**静态内部类**(推荐)
- 延迟加载
- 线程安全
- 性能好
- 实现优雅
2. **需要绝对安全**:使用**枚举类**(最推荐)
- 防止所有破坏方式
- 线程安全
- 代码简洁
3. **需要延迟加载且对性能要求不高**:使用**双重检查锁定**
- 延迟加载
- 线程安全
- 实现相对复杂
### 6.2 代码示例
**静态内部类(推荐):**
```java
public class Singleton {
private Singleton() {}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
```
**枚举类(最推荐):**
```java
public enum Singleton {
INSTANCE;
public void doSomething() {
// 业务方法
}
}
```
## 七、总结
### 7.1 核心要点
Java中实现单例模式的要点:
1. **四种基本要求**:私有构造、静态方法、线程安全、防止破坏
2. **六种实现方式**:懒汉式、双重检查锁定、饿汉式、静态代码块、静态内部类、枚举类
3. **三种推荐方式**:双重检查锁定、静态内部类、枚举类
4. **防护措施**:防止反射、反序列化、克隆破坏
### 7.2 关键理解
**关键理解:**
- **没有完美的方案**:需要在延迟加载、性能、安全性之间权衡
- **枚举类最安全**:能够防止所有破坏方式
- **静态内部类最优雅**:延迟加载、性能好、实现简洁
- **根据场景选择**:根据实际需求选择合适的实现方式
### 7.3 面试回答要点
**在回答这个问题时,需要说明:**
1. **单例模式定义**:一个类只有一个实例,提供全局访问点
2. **四个基本要求**:私有构造、静态方法、线程安全、防止破坏
3. **多种实现方式**:至少说明6种实现方式
4. **推荐方案**:静态内部类和枚举类
5. **防护措施**:如何防止反射、反序列化、克隆破坏