单例设计模式(Java)
(部分内容参考于菜鸟教程当中关于单例模式的说明)
什么是单例设计模式?
单例模式(Singleton Pattern)是一种常见的设计模式,其主要目的是确保一个类在系统中只有一个实例,并提供全局访问点。使用单例模式的场景一般是需要共享资源的情况,比如数据库连接池、线程池等。通过控制实例的创建,单例模式能够避免频繁创建和销毁全局使用的类实例,从而节省系统资源。
单例模式的关键特性
- 唯一性:单例类只能有一个实例。
- 自我管理:单例类必须自己创建自己的唯一实例。
- 全局访问:单例类必须给所有其他对象提供这一实例。
如何解决
单例模式通过检查系统中是否已存在该实例来解决问题。如果存在,则返回该实例;如果不存在,则创建一个新实例。
关键代码
单例类的构造函数通常是私有的,以防止外部直接实例化。
优点
- 内存效率:内存中只有一个实例,减少内存开销,特别是在频繁创建和销毁实例时(如管理学院首页页面缓存)。
- 资源占用:避免资源的多重占用(如写文件操作)。
缺点
- 接口限制:没有接口,不能继承。
- 职责冲突:与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心实例化方式。
结构
单例模式包含以下几个主要角色:
- 单例类:包含单例实例的类,通常将构造函数声明为私有。
- 静态成员变量:用于存储单例实例的静态成员变量。
- 获取实例方法:静态方法,用于获取单例实例。
- 私有构造函数:防止外部直接实例化单例类。
- 线程安全处理:确保在多线程环境下单例实例的创建是安全的。
单例设计模式的实现
懒汉式
懒汉式是在第一次使用时才创建实例。这种方式的优点是延迟加载,但在多线程环境下需要考虑线程安全的问题。下面是一个简单的懒汉式单例示例:
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() { }
public static synchronized LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
代码解释:
private static LazySingleton instance;
:定义一个静态变量instance
,用于存储单例实例。由于使用了private
修饰符,外部无法直接访问该变量。private LazySingleton() { }
:构造方法为私有,防止外部通过构造函数创建多个实例。public static synchronized LazySingleton getInstance() {...}
:静态方法,用于获取单例实例。使用synchronized
关键字确保线程安全,即使在多线程环境下也能正确创建示例。if (instance == null)
:检查实例是否已被创建。如果未创建,则实例化一个对象。
通过懒汉式单例模式确保了LazySingleton
类只有一个实例,并提供了一个全局访问点getInstance
来获取这个实例。这种方式在系统第一次调用该类时才创建对象,避免了不必要的资源开销。
饿汉式
与懒汉式不同,饿汉式在类加载的时候就创建实例,因此不用考虑多线程的同步问题。实现代码如下:
public class EagerSingleton {
private static final EagerSingleton instance = new EagerSingleton();
private EagerSingleton() { }
public static EagerSingleton getInstance() {
return instance;
}
}
代码解释:
private static final EagerSingleton instance = new EagerSingleton();
:在类加载时就创建实例,静态初始化实例,且用final
修饰,保证instance
在程序运行过程中不会被重新赋值。private EagerSingleton() { }
:构造方法为私有,防止外部通过构造函数创建多个实例。public static EagerSingleton getInstance() { return instance; }
:静态方法,用于获取单例实例,直接返回已创建的instance
。
通过饿汉式单例模式确保EagerSingleton
类的唯一实例在类加载时就创建,并提供全局访问点getInstance
。虽然这种方式在多线程环境下是安全的,但如果未使用就会浪费资源。
双重检查锁定
双重检查锁定是懒汉式的一种优化形式,通过两次检查来避免不必要的加锁,提高性能:
public class DoubleCheckedLockingSingleton {
private static volatile DoubleCheckedLockingSingleton instance;
private DoubleCheckedLockingSingleton() { }
public static DoubleCheckedLockingSingleton getInstance() {
if (instance == null) {
synchronized (DoubleCheckedLockingSingleton.class) {
if (instance == null) {
instance = new DoubleCheckedLockingSingleton();
}
}
}
return instance;
}
}
代码解释:
private static volatile DoubleCheckedLockingSingleton instance;
:定义一个可被volatile
修饰的静态变量,确保多线程环境下的可见性与防止指令重排序。private DoubleCheckedLockingSingleton() { }
:构造方法为私有,防止外部通过构造函数创建多个实例。public static DoubleCheckedLockingSingleton getInstance()
:静态方法用于获取单例实例。if (instance == null)
:第一次检查,确保在实例未创建时进入同步块。synchronized (DoubleCheckedLockingSingleton.class)
:进入同步块,保证同一时刻仅一个线程可以访问。if (instance == null)
:第二次检查,如果在进入同步块后实例仍未创建,则创建一个新实例。
通过双重检查锁定的方式,确保DoubleCheckedLockingSingleton
类只有一个实例,并提供了一个全局访问点getInstance
。该方式在多线程环境下减少了加锁的次数,提升了性能。
枚举单例
Java 1.5引入了枚举类型,枚举单例是实现单例模式的推荐方式,因为它天然支持序列化与线程安全:
public enum EnumSingleton {
INSTANCE;
public void someMethod() {
// 方法实现
}
}
代码解释:
public enum EnumSingleton { INSTANCE; }
:使用枚举类型创建单例,INSTANCE
为唯一实例。public void someMethod() { ... }
:示例中的方法,可以添加具体实现,以供使用。
使用枚举实现单例模式,通过EnumSingleton.INSTANCE
进行调用,天然的线程安全与序列化支持,使得这种方式成为实现单例模式的最佳实践。既避免了反射问题,也简化了代码逻辑。
单例模式的应用实例:学生信息管理系统
接下来,我们将通过实现一个简易的学生信息管理系统来体现单例设计模式的应用,具体使用懒汉式单例模式。
学生信息管理器的实现
import java.util.ArrayList;
import java.util.List;
public class StudentManager {
private static StudentManager instance;
private List<Student> students;
private StudentManager() {
students = new ArrayList<>();
}
public static synchronized StudentManager getInstance() {
if (instance == null) {
instance = new StudentManager();
}
return instance;
}
public void addStudent(Student student) {
students.add(student);
}
public void removeStudent(Student student) {
students.remove(student);
}
public List<Student> getStudents() {
return students;
}
public Student getStudentById(int id) {
for (Student student : students) {
if (student.getId() == id) {
return student;
}
}
return null;
}
}
class Student {
private int id;
private String name;
public Student(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
}
代码解释
-
单例实现:
private static StudentManager instance;
:定义一个静态变量来保存唯一实例。private StudentManager()
:构造器私有,防止外部通过构造函数创建多个实例。public static synchronized StudentManager getInstance()
:提供全局访问点,以获取唯一的StudentManager
实例,并使用synchronized
关键字确保线程安全。
-
增加/删除学生:
addStudent(Student student)
:将学生对象添加到列表中。removeStudent(Student student)
:移除指定的学生对象。
-
查询学生:
getStudents()
:返回当前管理的所有学生。getStudentById(int id)
:根据学生ID查找并返回相应的学生对象。
使用示例
现在我们可以在程序中使用这个学生信息管理器来管理学生信息:
public class Main {
public static void main(String[] args) {
StudentManager manager = StudentManager.getInstance();
Student student1 = new Student(1, "Alice");
Student student2 = new Student(2, "Bob");
manager.addStudent(student1);
manager.addStudent(student2);
System.out.println("所有学生:");
for (Student student : manager.getStudents()) {
System.out.println("- " + student.getName());
}
manager.removeStudent(student1);
System.out.println("删除后所有学生:");
for (Student student : manager.getStudents()) {
System.out.println("- " + student.getName());
}
}
}
代码运行解释
- 在
Main
类中,通过StudentManager.getInstance()
方式获取唯一的学生管理器实例。 - 使用管理器添加学生对象,并通过
getStudents()
方法输出所有学生的姓名。 - 删除一个学生后,再次调用
getStudents()
方法查看剩余学生,确保了信息管理的一致性。