单例设计模式
目录
单例设计模式
在 Java 中, 单例模式 (Singleton Pattern)是一种常用的设计模式,它确保一个类只有一个实例,并提供一个全局访问点来访问该实例。在实现单例模式时,有几种不同的加载方式,主要包括 懒汉式 (Lazy Initialization)和 饿汉式 (Eager Initialization)。
单例模式的加载方式
- 饿汉式单例 (Eager Initialization)
- 懒汉式单例 (Lazy Initialization)
- 双重检查锁定(Double-Checked Locking) 单例
- 静态内部类单例 (Bill Pugh Singleton)
- 枚举单例 (Enum Singleton)
下面会详细介绍这些实现方式。
1. 饿汉式单例 (Eager Initialization)
饿汉式单例的特点是,在类加载时就创建单例对象,确保线程安全,不需要任何同步机制。这种方式在类加载时就创建实例,因此无法进行延迟加载。
实现方式:
java
public class Singleton {
// 在类加载时就创建实例
private static final Singleton instance = new Singleton();
// 私有化构造函数
private Singleton() {}
// 提供公共访问方法
public static Singleton getInstance() {
return instance;
}
}
java
解释:
- 线程安全 :由于实例是在类加载时就创建的,确保了线程安全。
- 缺点 :即使实例从未使用,类加载时实例也会被创建,浪费了资源。
2. 懒汉式单例 (Lazy Initialization)
懒汉式单例的特点是实例是在第一次调用
getInstance()
时创建的,采用延迟加载。通常需要加锁以保证线程安全。
实现方式:
java
public class Singleton {
// 实例对象,未初始化
private static Singleton instance;
// 私有化构造函数
private Singleton() {}
// 提供公共访问方法
public static synchronized Singleton getInstance() {
// 如果实例为空,则创建实例
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
java
解释:
- 延迟加载 :实例只会在第一次使用时被创建,节省了资源。
- 线程安全
:使用了
synchronized
关键字来保证线程安全。 - 缺点
:每次调用
getInstance()
都会进行同步,性能较差。
3. 双重检查锁定(Double-Checked Locking) 单例
双重检查锁定的懒汉式单例,通过两次
if (instance == null)
检查来减少加锁的次数,提升性能。第一层检查不加锁,第二层检查加锁后创建实例。
实现方式:
java
public class Singleton {
// 使用 volatile 关键字确保对象的正确创建
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;
}
}
java
解释:
- 延迟加载 :实例只会在第一次调用时被创建。
- 性能优化 :第一次检查不加锁,只有在实例为空时才进行加锁,减少了加锁的开销。
- 线程安全
:使用
synchronized
保证线程安全,且volatile
关键字确保实例初始化的正确性。 - 缺点 :代码较为复杂,需要理解双重检查锁定的实现原理。
4. 静态内部类单例 (Bill Pugh Singleton)
静态内部类单例是一种非常高效且线程安全的单例实现方式,它利用了 类加载机制 来保证线程安全,且没有加锁的性能问题。
实现方式:
java
public class Singleton {
// 静态内部类实现单例
private static class SingletonHelper {
// 静态初始化器,由JVM来保证线程安全
private static final Singleton INSTANCE = new Singleton();
}
// 私有化构造函数
private Singleton() {}
// 提供公共访问方法
public static Singleton getInstance() {
return SingletonHelper.INSTANCE;
}
}
java
解释:
- 线程安全 :静态内部类在第一次加载时才会被初始化,而类加载是线程安全的。
- 性能 :静态内部类只有在首次使用时才会被加载,避免了不必要的同步操作。
- 优点 :没有同步的开销,且实现简单。
- 缺点 :需要理解类加载的机制。
5. 枚举单例 (Enum Singleton)
使用
enum
实现单例模式是最简单也是最安全的方式。由于 Java 枚举的特性,JVM 会保证枚举实例的唯一性,同时会确保线程安全。根据
Joshua Bloch
(《Effective Java》的作者)的建议,这是实现单例模式的最佳方式。
实现方式:
java
public enum Singleton {
INSTANCE;
// 可以添加其他方法
public void someMethod() {
System.out.println("Hello, Singleton!");
}
}
解释:
- 线程安全 :Java 枚举类本身就是线程安全的。
- 实现简单 :使用枚举类避免了许多复杂的代码和同步问题。
- JVM 保证 :JVM 在加载枚举类型时会保证它只会加载一次,并且是线程安全的。
- 优点 :最简单,最安全,且是 JDK 推荐的单例实现方式。
- 缺点 :无法延迟实例化,单例是预先创建的。
总结:懒汉式 vs 饿汉式
懒汉式单例 (Lazy Initialization):
- 优点 :延迟加载,只有在需要时才创建实例,节省资源。
- 缺点 :需要加锁,性能差,特别是在多线程环境下。
- 线程安全
:需要使用
synchronized
或其他同步机制来保证线程安全。
饿汉式单例 (Eager Initialization):
- 优点 :简单实现,线程安全,类加载时即创建实例。
- 缺点 :即使没有使用该实例,也会创建,浪费资源。
- 线程安全 :类加载时创建实例,线程安全。
推荐使用方式
- 枚举单例 是最推荐的单例实现方式,简单、安全、可靠,避免了所有其他方式中的问题。
- 如果需要延迟加载且在多线程环境中使用,可以考虑 静态内部类单例 或 双重检查锁定 单例,它们是线程安全且性能较好的方案。