单例设计模式:
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
注意:
- 1、单例类只能有一个实例。
- 2、单例类必须自己创建自己的唯一实例。
- 3、单例类必须给所有其他对象提供这一实例。
作用:让一个类只能创建一个实例(因为频繁的创建对象,回收对象会造成系统性能下降。)。解决对象的唯一性,保证了内存中一个对象是唯一的 。
饿汉式:Hunary
步骤:1.私有化构造器 2.直接创建静态对象 3.创建一个静态的方法,供外部调用实例
优点:类初始化的时候,会立即加载该对象,线程天生安全,调用效率高。
缺点:无法避免被反射破解,不安全
懒汉式:LazyMan
步骤:1.私有化构造器,不会直接创建对象 2.向外暴露调用对象方法,在方法中先进行对象判空,为空才创建对象,这就是懒汉式
优点:类初始化时,不会初始化该对象,真正需要使用的时候才会去创建该对象,具备懒加载功能。
缺点:
1.判空浪费时间
2.不加Synchronized的懒汉式线程不安全,需要用到volatile关键字保持new对象的原子性一致
懒汉式单线程下是OK的,但是多线程并发下不安全。
每次启动线程数都不一致,线程不安全。
怎么解决:
Tips:懒汉式也是可以实现线程安全的:只要加上Synchronized加锁即可:
但是这样一来,会降低整个访问的速度,而且每次都要判断。那么有没有更好的方式来实现呢?
解决方案:
double-check-lock双重加锁
所谓双重加锁机制。指的是,并不是每次进入getInstance方法都需要同步,而是先不同步,进入方法之后先检查实例是否存在,如果不存在才进入下面Synchronized加锁,之后会再次检查实例是否存在,如果还不存在才创建实例。
双重检测方式(因为JVM本身重排序的原因,可能会出现多次的初始化)
这种模式下的懒汉式,称为DCL懒汉式!
你以为这样就没问题了?大错特错,注意:这里的new对象操作不是原子性的
new 一个对象的执行顺序 ↓
* 1.分配内存空间
* 2.执行构造方法初始化对象
* 3.把这个对象指向内存空间
* 这是创建对象的步骤 正确顺序为 123
* 举例:
* 线程A 创建对象 步骤为 123, 我们的代码没有问题
* 线程B 创建对象 因为操作步骤不是原子性的,可能会走成 1 3 2,先指向了内存空间,再去构造对象
* 这时会出现一个问题,指令重排:双重检测模式会失效,因为此时我们的类,不为空了,但是里面返回的对象是空的。
* 所以就要用到一个关键字volatile,避免指令重排,保持操作原子性
*
静态内部类模式:
步骤:1. 私有化构造器 2. 创建一个静态内部类,类中创建外部类的实例 3.向外暴露一个方法获取静态内部类中创建的对象
优点:结合了懒汉式和饿汉式各自的优点,真正需要对象的时候才会加载,加载类是线程安全的。
缺点:每次调用都会创建多余的对象
以上单例设计模式都会被反射破解,枚举不会!
枚举:枚举本身是一个类,继承了Enum的实例就成为了枚举类
使用枚举实现单例模式,实现简单、调用效率高,枚举本身就是单例,由JVM从根本上提供保障,避免通过反射和反序列化的漏洞,缺点是没有延迟加载。
在源码Constructor中
如果是对象是通过反射机制创建的会抛出一个异常
IllegalArgumentException: Cannot reflectively create enum objects
枚举类在traget输出的代码中,构造器是空的,隐藏起来了,用javap -p 反编译也看不见构造器
我们用反射机制获取空构造器会获取不到对象,这时候用jad反编译工具,得到的java文件中,
可以看见构造器其实是有数据的,我们把数据放到反射中实现,就会得到这个异常Cannot reflectively create enum objects
由此可见,反射不能破坏枚举的单例模式
优点:实现简单,线程安全,防止反射攻击等。
缺点: 在不需要的时候可能就加载了,造成内存浪费
Tips:利用反射破解懒汉式。懒汉式不是安全的!
私有化构造器的时候判断外面的字段guoqing是否==false,等于的话就设置为true,如果不等于的话就抛出运行异常,这样设计的话在我们用反射机制获取构造器的时候,就获取不到对象了
但是有解决方法:利用反射强大的机制,假如我们反编译,知道要破解的字段是guoqing,那我们直接用反射获取guoqing字段,然后设置解除私有化限制,在创建构造器实例的时候,把guoqing设置为true,就可以破解DCL懒汉式了
仅供参考,依照Bilibili 狂神说 编写,如有侵权联系删除。