目录
引言
单例模式是一种常用的软件设计模式,它保证一个类只有一个实例,并提供一个全局访问点。本文将详细介绍六种不同的单例模式实现方法,并通过具体的Java代码示例来展示它们的原理及优缺点。
1. 饿汉式(静态常量)
这种方法在类加载时就完成了初始化,因此避免了线程同步问题,但可能会浪费内存空间。
代码示例
public class SingletonEager {
// 在类加载时就创建实例
private static final SingletonEager INSTANCE = new SingletonEager();
private SingletonEager() {}
/**
* 提供获取实例的方法
* @return 单例对象
*/
public static SingletonEager getInstance() {
return INSTANCE;
}
}
优点
- 实例化在类加载时完成,无需额外同步处理。
- 实现简单。
缺点
- 类加载时即创建实例,可能会造成资源浪费。
2. 懒汉式(非线程安全)
该方法在第一次调用 getInstance()
方法时创建实例。这种方式是最基本的懒汉式实现,但在多线程环境下可能会出现问题。
代码示例
public class SingletonLazy {
private static SingletonLazy instance;
private SingletonLazy() {}
/**
* 获取单例对象
* @return 单例对象
*/
public static SingletonLazy getInstance() {
if (instance == null) {
instance = new SingletonLazy();
}
return instance;
}
}
优点
- 实例化延迟到首次调用时,节省资源。
缺点
- 非线程安全,在多线程环境中可能会导致多个实例被创建。
3. 懒汉式(线程安全,同步方法)
为了解决懒汉式的线程安全问题,可以在 getInstance()
方法上加锁,但这会导致性能下降。
代码示例
public class SingletonLazySync {
private static SingletonLazySync instance;
private SingletonLazySync() {}
/**
* 获取单例对象
* @return 单例对象
*/
public static synchronized SingletonLazySync getInstance() {
if (instance == null) {
instance = new SingletonLazySync();
}
return instance;
}
}
优点
- 线程安全。
缺点
- 每次调用
getInstance()
都需要同步,影响性能。
4. 双重检查锁定
双重检查锁定是解决线程安全问题同时保持高效率的一种方式。
代码示例
public class SingletonDCL {
private volatile static SingletonDCL instance;
private SingletonDCL() {}
/**
* 获取单例对象
* @return 单例对象
*/
public static SingletonDCL getInstance() {
if (instance == null) {
synchronized (SingletonDCL.class) {
if (instance == null) {
instance = new SingletonDCL();
}
}
}
return instance;
}
}
优点
-
线程安全,且只在必要时同步。
-
性能较高。
缺点
- 实现较为复杂。
5. 静态内部类
利用Java的类加载机制保证初始化实例时只有一个线程,既解决了线程安全问题又保证了效率。
代码示例
public class SingletonInnerClass {
private SingletonInnerClass() {}
private static class SingletonHolder {
private static final SingletonInnerClass INSTANCE = new SingletonInnerClass();
}
/**
* 获取单例对象
* @return 单例对象
*/
public static SingletonInnerClass getInstance() {
return SingletonHolder.INSTANCE;
}
}
优点
-
线程安全,无需同步。
-
实现简单,延迟加载。
缺点
-
相对于饿汉式来说,实现稍微复杂一些。
6. 枚举
枚举类型的实例天然就是单例的,并且可以防止反射和序列化带来的问题。
代码示例
public enum SingletonEnum {
INSTANCE;
public void someMethod() {
// 实现方法
}
}
优点
-
自然地支持单例模式。
-
保证线程安全。
-
防止反射攻击。
缺点
- 如果不需要其他特性,仅使用枚举作为单例可能显得有些过头。
结论
根据上述分析,每种单例模式的实现方式都有其特定的适用场景。如果要在这些实现方式中推荐一种最常用且适用性较广的方式,我会推荐 静态内部类 实现方式。以下是推荐的理由:
推荐理由 - 静态内部类
-
线程安全性:静态内部类利用了Java类加载机制的特性,保证了实例化的线程安全性。由于静态内部类只会被加载一次,因此可以确保实例化过程只发生一次,不会出现多线程环境下的并发问题。
-
延迟加载:静态内部类中的实例是在首次调用
getInstance()
方法时才被创建的,实现了延迟加载,这样可以在需要的时候才创建实例,节省了不必要的内存占用。 -
简洁性:相比于双重检查锁定等实现方式,静态内部类的实现更为简洁明了,易于理解。
-
效率:静态内部类在多线程环境中表现良好,因为它不需要在每次调用
getInstance()
时都进行同步操作。 -
可扩展性:如果将来需要扩展单例类的功能,静态内部类的实现方式也相对容易扩展,例如添加更多的静态成员变量或方法。