文章目录
- 一、如何手写一个单例模式
- 1.1 什么是单例模式
- 1.2 单例模式的适用场景
- 1.3 单例模式的设计原理
- 1.4 单例模式的实现方案
- 1.5 代码测试
一、如何手写一个单例模式
1.1 什么是单例模式
单例模式是一种常用的软件设计模式,它的核心是只包含一个实例的特殊类。单例模式可以保证系统中一个类只有一个实例,对实例个数进行控制可以节约节省内存,加快对象的访问速度。
1.2 单例模式的适用场景
资源共享:当系统中需要共享某个资源,并且该资源只能被一个对象访问和修改时,可以使用单例模式。例如,一个数据库连接池,多个线程需要访问数据库连接,但是连接数量有限,此时可以使用单例模式确保只有一个连接池对象。
对象控制:在某些情况下,系统需要限制某个类只能拥有一个实例。例如,一个系统中只能有一个配置文件读取对象,以确保配置信息的一致性和避免重复读取。
日志记录器:在日志记录器中,由于多个对象可能同时需要写入日志,为了避免产生混乱的日志信息,可以使用单例模式。单例模式可以确保只有一个日志记录器实例存在,并且所有对象共享同一个实例进行日志记录。
缓存、对话框框架等:在一些需要频繁创建和销毁对象的场景中,为了提高性能,可以使用单例模式来管理对象的生命周期和减少资源消耗。
1.3 单例模式的设计原理
将构造函数设置为 private,避免外部通过 new 创建实例,创建一个静态方法返回单例类对象 ,同时需要考虑对象创建时的线程安全问题,确保单例类的对象 有且仅有一个 ,尤其是在多线程环境下, 确保单例类对象在反序列化时不会重新构建对象。
1.4 单例模式的实现方案
- 创建一个 Siglegle 类,类中有一个本类的实例成员属性,该实例使用 static(原因看第 3 点) 和 volatile 进行修饰,volatile 是避免出现指令重排。因为如果指令重排线程 1 在创建实例的时候可能会先分配内存和指向分配再进行初始化,而线程 2 可能在线程 1 执行到初始化之前就对实例对象进行判断是否为空,因为分配了指向此时是不为空的,那么线程 2 可能就会返回一个还未初始化的实例;
- 编写构造函数,将 private 作为构造函数的访问修饰符,构造函数内部打印一句话是为了判断调用了几次构造函数;
- 编写一个静态方法用于对象实例的创建,因为第一次创建之前是没有实例的,如果不是静态方法就无法进行访问,因为静态方法只能访问静态成员故 siglegle 对象也需要是静态的。
- 在创建之前先判断是否为空,如果为空就进行创建;
- 在对象加锁避免两个线程同时运行;
- 这里很多人搞不清楚为什么有第二次 if 判断,其实很简单,假设线程 1 使用了锁但还未创建对象,同时线程 2 判断实例为空也进行对锁的申请,而等线程 1 创建完对象之后放弃锁的使用权,这时候线程 2 获得了锁的使用权,又进行一次对象的创建,此时就会出现两个实例,破坏了单例模式;
- 最后返回 siglegle 对象;
public class Siglegle {
private static volatile Siglegle siglegle;
private Siglegle() {
// TODO Auto-generated constructor stub
System.out.println("创建了一个实例");
}
public static Siglegle createSiglegle() {
if(siglegle == null)
synchronized(Siglegle.class) {
if (siglegle == null)
siglegle = new Siglegle();
}
return siglegle;
}
}
1.5 代码测试
测试规则:创建两个线程,每个线程调用 10 次 Siglegle 的创建对象方法,最后通过控制台的输出条数来判断共调用了多少次构造方法;
测试代码:
public class TestCreate {
public static void main(String[] args) {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 10; i++)
Siglegle.createSiglegle();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 10; i++)
Siglegle.createSiglegle();
}
});
thread1.start();
thread2.start();
}
}
测试结果:
测试分析:两个线程分别调用 10 次 Siglegle 的创建对象方法,执行完毕后控制台共出现 1 条输出,即 Siglegle 的构造方法只被调用了一次,本次测试对象无错误可使用;