/**
* 功能:单例类<br>
*
* @author 袁超华
* @since 2022/7/4 16:24
* <p>
* <p>
* <p>
* 懒汉式单例类实现步骤:<br>
* 1. 提供私有静态变量instance存放当前类实例<br>
* 2. 构造器私有化,不让外部访问<br>
* 3. 提供静态getInstance方法获取实例,在该方法中进行判断instance为空创建对象,不为空直接返回<br>
* =================================================================<br>
* 总结:<br>
* <b>这是一个单列的标准实现方式,但是这种实现方式是线程不安全的, 也不是反射安全的, 也不是序列化安全的</b> <br>
* <br>
* <b>为什么是线程不安全的:</b><br>
* 1. getInstance() 方法不是一个线程安全的方法.<br>
* 2. 在多线程访问的情况下, 有可能两个线程同时判断为空, 分别创建新的对象<br>
* <br>
* <b>为什么不是反射安全的:</b><br>
* 1. 我们可以通过Class 获取构造并破解私有权限<br>
* 2. 通过构造器对象的newInstance方法可以创建对象<br>
* <b>为什么不是序列化的:</b><br>
* 1. 我们通过序列化把对象序列化成二进制数据流输出到网络或文件中<br>
* 2. 那么再通过反序列化把网络中或文件中的二进制数据流在反序列成对象<br>
* 3. 那么反序列成的对象就不是原来的对象了
* ———————————————————————————————————————————<br>
* 解决方案:<br>
* <b>线程安全:</b><br>
* 1. 同步方法: 缺点-性能低,不管对象有没有创建, 每次获取对象都会加锁<br>
* 2. 同步代码块: 问题 - 需要优化, 进行优化和使用同步方法没有什么区别<br>
* <b>反射安全:</b><br>
* 1. 需要在构造器添加判断<br>
* <b>序列化安全:</b><br>
* 1. 在需要序列化的实体类上加一个字段 private static final long serialVersionUID 并指定值,其值可以使用idea工具生成<br>
* 2. 在类中添加如下方法<br>
* <pre>
* <b>private Object readResolve() {
* return instance;
* }</b>
* </pre>
* =================================================================<br>
*/
public class LazySingleton {
/**
* 静态变量instance存放当前类实例
*/
private static volatile LazySingleton instance;
/**
* 构造器私有化,不让外部访问<br>
* =================================================================<br>
* <b>注意:私有化构造器也并不能绝对的保证单例</b><br>
* 1. 我们可以通过当前类或者当前实例获取Class 对象
* <pre>
* <b>Class<LazySingleton2> class1 = LazySingleton2.class;
* Class<? extends LazySingleton2> clazz2 = LazySingleton2.getInstance().getClass();</b>
* </pre>
* <p>
* 2. 通过Class 对象的 getDeclaredConstructor() 获取它的构造器对象
* <pre>
* <b>Constructor<LazySingleton2> constructor1 = class1.getDeclaredConstructor();
* Constructor<? extends LazySingleton2> constructor2 = clazz2.getDeclaredConstructor();</b>
* </pre>
* 3. 再调用构造器对象的setAccessible();传值为true,来破解private权限
* <pre>
* <b>constructor1.setAccessible(true);
* constructor2.setAccessible(true)</b>
* </pre>
* 4. 然后通过构造器对象的 newInstance()方法创建对象,再把创建出的对象进行比较或输出就不是同一个对象了.
* <pre>
* <b>LazySingleton2 instance1 = constructor1.newInstance();
* LazySingleton2 instance2 = constructor2.newInstance();
*
* System.out.println(instance1);//输出:LazySingleton2@3d075dc0
* System.out.println(instance2);//输出:LazySingleton2@214c265e
* System.out.println(instance1==instance2);//输出:false 说明他们不是同一个实例</b>
* </pre>
* <b>5. 解决这个问题只需要在构造器里面,判断一下存放当前类实例的变量instance是否为空,不为空抛异常为空放行</b>
* <pre>
* <b>private LazySingleton2() {
* if (null!=instance){
* throw new RuntimeException("单例模式只允许有一个实例");
* }
* }</b>
* </pre>
* 6. 优化完成之后,再走上面的流程,就不会再多例了
*/
private LazySingleton() {
if (null != instance) {
throw new RuntimeException("单例模式只允许有一个实例");
}
}
/**
* getInstance方法获取实例步骤:<br>
* 1. 判断instance是否我空<br>
* 2. 不为空返回instance<br>
* 3. 为空调用私有构造器,赋值给instance,返回 instance<br>
* =================================================================<br>
* <b>解决线程不安全问题:</b> <br>
* <b>1. 使用同步方法</b><br>
* <pre>
* public <b>synchronized</b> static LazySingleton2 getInstance() {
* if (null == instance) {
* instance = new LazySingleton2();
* }
* return instance;
* }
* </pre>
* 总结:<br>
* 使用这种方式造成的问题是,无论instance是否为空,只要是调用这个方法就会加锁,因此效率低<br>
* ———————————————————————————————————————————<br>
* <b>2. 使用同步代码块</b><br>
* 使用同步代码块需要考虑到在那个地方加锁,会效率高一些, 如下代码和使用同步方法没有什么区别<br>
* <pre>
* public static LazySingleton2 getInstance() {
* // 这里使用同步代码块对if判断进行加锁,这样加锁和使用同步方法没有什么区别
* // 为什么说没有什么区别呢:
* // 1. 每一次使用这个方法,都会走同步代码块,都会去判断instance是否为空
* // 2. 其实当instance不为空的时候就不需要再同步了
* <b>synchronized (LazySingleton2.class) {</b>
* if (null == instance) {
* instance = new LazySingleton2();
* }
* <b>}</b>
* return instance;
* }
* </pre>
* <b>可以使用下面这种进行优化:</b><br>
* 在同步代码块外面在加一层判断<br>
* <pre>
* public static LazySingleton2 getInstance() {
* // 在此处加一层判断, <b>同时instance也要使用volatile进行修饰:标记的变量不会使用优化功能 </b>
* // 初始化不会乱序优化 从而避免DCL失效
* // 在外层添加判断null的原因是:instance初始化后,不论单线程还是多线程同步代码块就不需要再执行了
* <b>if (null == instance) {</b>
* // 1. 在外面加一层判断,只有当instance为空了才会进入
* // 2. 当第一次初始化时, 多线程情况加可能会有多个线程同时判断为空
* // 3. 因此使用下面同步代码块进行加锁保证同步
* <b>synchronized (LazySingleton2.class) {</b>
* // 1. 保证同步的情况,只是线程同步了,线程通过外层判断有依然为空的情况
* // 例如: A,B两个线程: B和A线程同时在外层判断为空,但是B线程没有抢
* // 到锁没有先执行,只有A执行完成之后再执行,B在外层判断是为空的,但
* // 是A线程已经执行完成把instance初识化过了,也就是说B线程进入同步
* // 代码块后instance不为空.
* // 2. 因此需要在同步代码块内部再次判空, 只有当instance为空时初始化
* if (null == instance) {
* instance = new LazySingleton2();
* }
* <b>}</b>
* <b>}</b>
* return instance;
* }
* </pre>
*
* @return 返回实例
*/
public static LazySingleton getInstance() {
if (null == instance) {
synchronized (LazySingleton.class) {
if (null == instance) {
instance = new LazySingleton();
}
}
}
return instance;
}
}
1.单例模式
于 2022-07-06 13:58:06 首次发布