定义
单例模式(Singleton Pattern)是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点来访问该实例。这种模式的核心在于控制类的实例化过程,保证在任何时间点,一个类只有一个实例存在,并且这个实例可以被系统的所有其他部分通过一个公共的访问点访问。
1、唯一实例:单例模式要求一个类在任何情况下只能创建一个实例。这意味着无论多少次尝试创建对象,或者通过什么方式创建对象,最终都应当返回同一个实例。
2、全局访问点:单例模式提供了一个统一的接口或方法,允许程序的任何其他部分无需关心实例的创建细节,就能够访问到这个唯一的实例。
3、自我实例化:单例类负责自己的实例化过程。它通常会提供一个静态方法,如getInstance(),用于返回类的唯一实例。如果实例不存在,该方法会创建一个实例,并在后续的调用中返回这个实例。
4、私有构造函数:为了避免外部通过new关键字或其他方式创建新的实例,单例类的构造函数通常被声明为private,这样就只能由类本身来实例化。
5、线程安全:在多线程环境中,单例模式还需要确保线程安全,即在多个线程同时访问时,也能确保只创建一个实例。这通常通过同步机制(如 synchronized 关键字)或高级别的并发控制来实现。
6、序列化安全:单例模式还需要考虑序列化和反序列化的安全问题。为了确保反序列化不会创建新的实例,可以通过实现Serializable接口并定义readResolve()方法来保证实例的唯一性。
实现方式
特点 | 优点 | 缺点 | |
---|---|---|---|
懒汉式(线程不安全) | 第一次调用getInstance()方法时才创建实例。 | 实现了延迟加载,节省资源。 | 在多线程环境下可能创建多个实例,需要额外的同步机制来保证线程安全。 |
懒汉式(线程安全) | 第一次调用getInstance()方法时才创建实例,并使用synchronized关键字来保证线程安全。 | 在多线程环境下也能确保只创建一个实例。 | 每次调用getInstance()都需要进行同步,效率较低。 |
饿汉式 | 类加载时就完成实例化,避免了线程同步问题 | 实现简单,无需考虑线程同步问题 | 不管是否使用,都会占用资源,可能导致内存浪费 |
双重检查锁定 | 两次检查实例是否已创建,如果未创建则进行同步创建。 | 结合了懒汉式和饿汉式的优点,既节省资源又保证了线程安全 | 实现相对复杂,需要使用volatile关键字来防止指令重排 |
静态内部类 | 使用静态内部类来实现单例,利用类加载的机制保证线程安全 | 实现了延迟加载和线程安全,且实现简单 | 不能通过反射破坏单例模式,但需要了解类加载机制 |
枚举 | 使用枚举类型来实现单例,JDK内部保证每个枚举值只有一个实例 | 简单、线程安全,防止反序列化攻击 | 只能用于单例模式的实现,不能用于其他场景 |
注册式(使用登记表) | 使用一个全局的注册表来记录和管理单例实例 | 可以在运行时动态创建和管理单例实例 | 增加了系统的复杂性,需要额外的管理和维护 |
使用容器 | 利用依赖注入框架的单例作用域来管理单例实例 | 代码简洁,易于管理和测试 | 依赖于特定的框架,减少了代码的可移植性 |
代码示例
饿汉式
通过将构造函数设置为私有,确保外部无法直接通过new关键字创建实例。类内部创建一个该类的静态实例,并通过一个公共的静态方法返回这个实例。
package com.example.helloworld;
public class HungryMan {
//私有静态实例
private static final HungryMan instance = new HungryMan();
//私有构造函数,防止外部实例化
private HungryMan(){}
//公共静态方法,返回唯一实例
public static HungryMan getInstance(){
System.out.println("这是一个饿汉式单例");
return instance;
}
public static void main(String args[]){
HungryMan.getInstance();
}
}
这是一个饿汉式单例
懒汉式
通过将构造函数设置为私有,确保外部无法直接通过new关键字创建实例。类内部通常使用一个静态变量来保存实例,并设置为null初始值。通过一个公共的静态方法来获取实例,如果实例为null,则创建一个新实例,并将其赋值给静态变量;如果实例已经存在,则直接返回该实例。
package com.example.helloworld;
public class LazyMan {
//私有静态实例并设置初始值为null
private static LazyMan instance = null;
//私有函数,防止外部实例化
private LazyMan(){}
//公共静态方法,返回唯一实例
public static synchronized LazyMan getInstance(){
if (instance == null){
instance = new LazyMan();
System.out.println("创建了一个新实例");
}
else {System.out.println("实例已存在,直接返回");}
return instance;
}
public static void main(String args[]){
LazyMan.getInstance();
}
}
创建了一个新实例