目录
什么是单例模式
单例模式是一种创建型设计模式,它确保某个类只有一个实例,并提供一个全局访问点以访问该实例。单例模式的主要目的是确保一个类只有一个实例,并且提供一个访问该实例的方法,以便在整个应用程序中共享该实例。
简单来说就是,某个类全局只有一个实例,并提供一个访问该实例的方法。
几种实现方式
在 Java 中,单例模式通常有以下几种实现方式:
-
饿汉式单例: 在类加载时就创建唯一的实例,并通过静态方法或静态变量提供访问点。
-
懒汉式单例: 在第一次使用时创建唯一的实例,延迟实例化的过程。
-
双重检查锁单例: 在懒汉式的基础上添加双重检查锁,提高了线程安全性和性能。
-
静态内部类单例: 利用静态内部类的特性,在外部类被加载时并不会立即加载内部类,从而实现延迟加载和线程安全。
-
枚举单例: 利用枚举类的特性,枚举类型本身就是单例的,且线程安全。
单例模式的优点包括:
- 全局访问点: 提供了一个全局的访问点,方便在整个应用程序中访问该实例。
- 节省资源: 由于只有一个实例,可以节省系统资源,减少了内存开销。
- 线程安全: 可以保证在多线程环境下,只会创建一个实例,避免了多线程访问导致的问题。
然而,单例模式也存在一些缺点,包括:
- 可能引起内存泄漏: 如果单例持有大量资源且在整个应用程序的生命周期内都不会释放,可能会导致内存泄漏。
- 可能降低了可扩展性: 单例模式将对象的创建和访问耦合在一起,可能会降低系统的灵活性和可扩展性。
- 可能增加了单元测试的复杂性: 在某些情况下,单例模式可能会增加单元测试的复杂性,特别是在需要模拟单例对象的行为时。
下面我们使用Java代码分别实现单例模式的多种变体。
饿汉式单例模式
饿汉式单例模式是指在类加载时就创建唯一的实例,并通过静态方法或静态变量提供访问点。以下是一个使用 Java 实现饿汉式单例模式的示例:
public class Singleton {
// 在类加载时就创建唯一的实例,并初始化为静态变量
private static final Singleton instance = new Singleton();
// 将构造方法私有化,防止外部类直接实例化
private Singleton() {}
// 提供一个公共的静态方法,用于获取唯一的实例
public static Singleton getInstance() {
return instance;
}
// 可以添加其他方法和属性
public void doSomething() {
System.out.println("Singleton instance is doing something.");
}
}
在这个示例中,Singleton
类的构造方法被私有化,因此外部类无法直接实例化该类。在类加载时,静态变量 instance
会被初始化为一个唯一的实例。
通过提供一个公共的静态方法 getInstance()
,客户端可以获取到唯一的实例。因为 instance
是静态的,所以它可以在整个应用程序中被共享。
使用饿汉式单例模式时要注意,由于实例在类加载时就已经创建,因此可能会导致在应用程序启动时就创建实例,而如果该实例在整个应用程序的生命周期中并不会被使用到,可能会造成资源浪费。
懒汉式单例模式
懒汉式单例模式是指在第一次使用时才创建唯一的实例,延迟实例化的过程。以下是一个使用 Java 实现懒汉式单例模式的示例:
public class Singleton {
// 声明一个静态的实例变量,但不进行实例化
private static Singleton instance;
// 将构造方法私有化,防止外部类直接实例化
private Singleton() {}
// 提供一个公共的静态方法,用于获取唯一的实例
public static synchronized Singleton getInstance() {
// 第一次调用时,进行实例化
if (instance == null) {
instance = new Singleton();
}
return instance;
}
// 可以添加其他方法和属性
public void doSomething() {
System.out.println("Singleton instance is doing something.");
}
}
在这个示例中,Singleton
类的构造方法被私有化,因此外部类无法直接实例化该类。在 getInstance()
方法中,通过检查静态变量 instance
是否为空来判断是否需要创建新的实例。如果 instance
为空,则在方法中创建新的实例;如果 instance
不为空,则直接返回已经存在的实例。
需要注意的是,为了确保在多线程环境下也能正确地工作,getInstance()
方法使用了 synchronized
关键字来保证线程安全。这会导致性能有所下降,因为每次调用 getInstance()
方法都会对整个方法加锁,影响了并发性能。
如果确定程序不会在多线程环境下使用,可以考虑去掉 synchronized
关键字,但需要注意在多线程环境下使用懒汉式单例模式时可能会出现线程安全问题,需要额外的措施来保证线程安全性。
双重检查锁单例模式
双重检查锁单例模式是对懒汉式单例模式的改进,在保证延迟实例化的同时,提高了线程安全性和性能。以下是一个使用 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;
}
// 可以添加其他方法和属性
public void doSomething() {
System.out.println("Singleton instance is doing something.");
}
}
在这个示例中,Singleton
类的构造方法被私有化,防止外部类直接实例化该类。instance
变量被声明为 volatile
关键字修饰,确保了线程之间的可见性和禁止指令重排序。
在 getInstance()
方法中,使用了双重检查锁的方式来确保线程安全性。在第一次检查时,如果 instance
为空,就进入同步块;在同步块中,再次检查 instance
是否为空,如果为空,则在同步块中创建新的实例。这样可以避免在每次调用 getInstance()
方法时都进行同步,提高了性能。
静态内部类单例模式
静态内部类单例模式利用了静态内部类的特性,在外部类被加载时并不会立即加载内部类,从而实现了延迟加载和线程安全。以下是一个使用 Java 实现静态内部类单例模式的示例:
public class Singleton {
// 将构造方法私有化,防止外部类直接实例化该类
private Singleton() {}
// 静态内部类
private static class SingletonHolder {
// 在静态内部类中声明并初始化一个静态的、final修饰的实例变量
private static final Singleton instance = new Singleton();
}
// 提供一个公共的静态方法,用于获取唯一的实例
public static Singleton getInstance() {
return SingletonHolder.instance;
}
// 可以添加其他方法和属性
public void doSomething() {
System.out.println("Singleton instance is doing something.");
}
}
在这个示例中,Singleton
类的构造方法被私有化,防止外部类直接实例化该类。静态内部类 SingletonHolder
中声明了一个静态的、final 修饰的实例变量 instance
,并在类加载时进行了初始化。
通过提供一个公共的静态方法 getInstance()
,客户端可以获取到唯一的实例。由于静态内部类的特性,只有在第一次调用 getInstance()
方法时才会加载内部类,从而实现了延迟加载的效果。同时,由于静态内部类的加载是线程安全的,因此在多线程环境下也能保证单例的唯一性。
枚举单例模式
枚举单例模式是利用枚举类型本身的特性,枚举类型本身就是单例的,且线程安全。以下是一个使用 Java 实现枚举单例模式的示例:
public enum Singleton {
INSTANCE; // 定义一个枚举常量,表示单例实例
// 可以添加其他方法和属性
public void doSomething() {
System.out.println("Singleton instance is doing something.");
}
}
在这个示例中,Singleton
是一个枚举类型,它只有一个枚举常量 INSTANCE
,表示单例实例。
由于枚举类型的特性,枚举类型在 Java 中是线程安全的,且在类加载时就会被初始化,因此可以保证单例的唯一性。在枚举类型中,INSTANCE
是一个静态的、final 修饰的实例,因此可以通过 Singleton.INSTANCE
来获取单例实例。
客户端可以通过 Singleton.INSTANCE
来获取唯一的实例,而且不需要考虑多线程环境下的线程安全性问题。
非常的实用,喜欢的小伙伴可以动动你们发财的小手,给博主一个小小的点赞或者关注,就是对博主最大的鼓励,爱你们哦~~