用简单易懂的方式手写一个单例模式,看了不会来打我

本文介绍了单例模式的概念、适用场景、设计原理,详细讲解了Java中线程安全的单例模式实现,并提供了测试代码来验证其正确性。单例模式常用于资源共享、对象控制以及日志记录等场景,确保系统中类的实例只有一个。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、如何手写一个单例模式

1.1 什么是单例模式

  单例模式是一种常用的软件设计模式,它的核心是只包含一个实例的特殊类。单例模式可以保证系统中一个类只有一个实例,对实例个数进行控制可以节约节省内存,加快对象的访问速度。

1.2 单例模式的适用场景

资源共享:当系统中需要共享某个资源,并且该资源只能被一个对象访问和修改时,可以使用单例模式。例如,一个数据库连接池,多个线程需要访问数据库连接,但是连接数量有限,此时可以使用单例模式确保只有一个连接池对象。

对象控制:在某些情况下,系统需要限制某个类只能拥有一个实例。例如,一个系统中只能有一个配置文件读取对象,以确保配置信息的一致性和避免重复读取。

日志记录器:在日志记录器中,由于多个对象可能同时需要写入日志,为了避免产生混乱的日志信息,可以使用单例模式。单例模式可以确保只有一个日志记录器实例存在,并且所有对象共享同一个实例进行日志记录。

缓存、对话框框架等:在一些需要频繁创建和销毁对象的场景中,为了提高性能,可以使用单例模式来管理对象的生命周期和减少资源消耗。

1.3 单例模式的设计原理

  将构造函数设置为 private,避免外部通过 new 创建实例,创建一个静态方法返回单例类对象 ,同时需要考虑对象创建时的线程安全问题,确保单例类的对象 有且仅有一个 ,尤其是在多线程环境下, 确保单例类对象在反序列化时不会重新构建对象。

1.4 单例模式的实现方案

  1. 创建一个 Siglegle 类,类中有一个本类的实例成员属性,该实例使用 static(原因看第 3 点) 和 volatile 进行修饰,volatile 是避免出现指令重排。因为如果指令重排线程 1 在创建实例的时候可能会先分配内存和指向分配再进行初始化,而线程 2 可能在线程 1 执行到初始化之前就对实例对象进行判断是否为空,因为分配了指向此时是不为空的,那么线程 2 可能就会返回一个还未初始化的实例;
  2. 编写构造函数,将 private 作为构造函数的访问修饰符,构造函数内部打印一句话是为了判断调用了几次构造函数;
  3. 编写一个静态方法用于对象实例的创建,因为第一次创建之前是没有实例的,如果不是静态方法就无法进行访问,因为静态方法只能访问静态成员故 siglegle 对象也需要是静态的。
  4. 在创建之前先判断是否为空,如果为空就进行创建;
  5. 在对象加锁避免两个线程同时运行;
  6. 这里很多人搞不清楚为什么有第二次 if 判断,其实很简单,假设线程 1 使用了锁但还未创建对象,同时线程 2 判断实例为空也进行对锁的申请,而等线程 1 创建完对象之后放弃锁的使用权,这时候线程 2 获得了锁的使用权,又进行一次对象的创建,此时就会出现两个实例,破坏了单例模式;
  7. 最后返回 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 的构造方法只被调用了一次,本次测试对象无错误可使用;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jc_caterpillar

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值