设计模式——单例模式

单例模式

定义

确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

即保证一个类只有一个实例,并且提供一个全局访问点

优缺点、应用场景

优点

  1. 单例对象在内存中只有一个实例,减少了内存的开支。尤其对于一个频繁创建、销毁的对象时,单例模式的优势就更明显。
  2. 减少系统的性能开销。一个对象的创建需要占用较多资源(例如:读取配置信息、产生其他依赖)时,可以在系统启动时产生单例对象,再通过永久驻留内存的方式来解决。
  3. 避免资源的多重占用。例如一个写文件的操作,单例模式可以避免对同一文件的同时写操作。

缺点

  1. 单例模式一般没有接口,拓展困难。
  2. 对测试不利,严格创建单例的环境中,只能在单例对象创建后才能进行测试。
  3. 单例模式与单一职责原则有冲突。

场景

  1. 重量级的对象,不需要多个实例,如线程池,数据库连接池。

实现方式

  • 懒汉模式
  • 饿汉模式
  • 静态内部类
  • 枚举类型

懒汉模式(外部类写法)

单线程下,只需要创建一次instance对象即可


多线程下,就有可能出现同时创建实例


解决方法:synchronized

优化(synchronized+双重非空校验)

虽然可以进行同步,但是并不是每一次都需要对来访的对象进行加锁,只有尝试创建时才需要加锁

java 复制代码
/**
 * 懒汉模式
 */
class LazyMan{
	private volatile static LazyMan instance;
	private LazyMan(){
	}

	public static LazyMan getInstance() {
		if (instance == null){
			// 如果两个以上线程检测到instance为null,则竞争一把类锁
			synchronized (LazyMan.class){
				if (instance == null){
					instance = new LazyMan();
				}
			}
		}
		return instance;
	}
}

反编译查看new的过程

步骤:

对.java文件进行:javac操作,得到.class文件

再对.class文件进行:javap -v操作

  1. 首先在堆空间创建该类的引用
  2. 将引用的内存地址复制到栈内存,压到栈顶
  3. 初始化构造方法(这里是无参构造方法)
  4. 将引用从栈中弹出
  5. 给对象赋值

重排序问题

根据反编译的步骤:分配空间、初始化、引用赋值,

这三步中的初始化和引用赋值是可以调换位置的。

但是如果赋值发生在初始化之前,则有可能出现空指针异常。所以要使用volatile,让线程到主存中访问数据,这样就不会出现null了。

懒汉模式小结

饿汉模式

初始化阶段就创建好一个对象,其他对象要访问,就只能访问这个对象。

本质是依赖JVM的类加载机制,确保实例的唯一性

饿汉模式小结

根据JVM的类加载过程:

其中准备过程是根据基本类型和对象类型进行初始化,基本类型如Integer就为0,String类型就为null。

而饿汉模式则是在JVM的初始化阶段唯一的对变量赋值,确保了对象的唯一。

静态内部类实现单例模式

即通过静态内部类的方式创建唯一实例,不提供公有的构造函数

并且只能通过公有的getInstance()方法获取私有对象

静态内部类实现小结

反射攻击

通过资源类的反射,获取到私有构造方法的使用权,创建实例。

结果返回false

解决方法一

在私有构造中判断instance是否已经创建,进行锁死条件,防止反射攻击

解决方法二

枚举法

根据反射newInstance 的源码,可以发现如果反射的类是添加了枚举enum类型的,则不允许创建该对象。

会抛出非法参数的异常


可以证明,枚举类型的反射是不允许创建对象的

序列化攻击

根据序列化的特点及其内部的实现原理,序列化与反序列化不会经过我们所指定的方法。所以可以通过序列化来进行攻击。

返回false,即序列化前后对象不一致,反序列化后再创建了一个对象。

解决方法:Serializable

重写方法readResolve(),并且设置序列化版本




这样就确保了序列化前后的对象是同一个。

枚举类的序列化问题

枚举类不存在序列化攻击的问题,跟反射攻击一样

根据ObjectInputStream类中提供的方法,可以发现枚举类型在进行反序列化时被加载到了类加载器中,收到JVM的保护。

不可变的类型


相关推荐
张小姐的猫11 小时前
【Linux】网络编程 —— HTTP协议(上)
linux·运维·服务器·网络·http·单例模式·策略模式
杨充13 小时前
10.可测试性实战设计
设计模式·开源·代码规范
杨充14 小时前
9.重构十二式的实战
设计模式·开源·代码规范
杨充14 小时前
6.设计原则的全景图
设计模式·开源·全栈
杨充14 小时前
2.面向对象的特性
设计模式
杨充14 小时前
7.SOLID原则案例汇
设计模式·开源·全栈
杨充14 小时前
8.反模式与坏味道
设计模式·开源·代码规范
杨充14 小时前
3.接口vs抽象类比较
设计模式
咖啡八杯17 小时前
文法、BNF与AST
java·设计模式·解释器模式·ast·文法