一、单例模式
(1)单例模式
(2)单例模式实现要点
(3)使用场景
当频繁创建、销毁某个对象时,可以考虑单例模式。
当创建对象消耗资源过多时,但又经常使用时,可以考虑单例模式。
(1)实现方式
(2)饿汉式、懒汉式 区别
二、饿汉式
(1)基本说明
(2)代码实现(静态变量)
public 修饰变量,直接通过 “类名.变量名” 的方式获取对象。
(3)代码实现(静态方法)
private 修饰变量,不允许通过 “类名.变量名” 的形式访问。
public 修饰方法,通过 “类名.方法名” 的方式获取对象。
(4)代码实现(静态代码块)
静态代码块,只是将实例化操作 移动到 静态代码块中进行实现。
(5)这就完了吗?
当然不是了,这样写只是防止了通过 new 实例化对象。
对象实例化的方式还有 反射、序列化、克隆 等操作。
这些操作是否会破坏单例模式?需要思考一下。
(1)类主动使用时,才会进行类的初始化
类只有主动使用时,才会进行初始化操作。并不一定使用到类,就会触发初始化操作。
比如:
进行反射获取私有构造方法时,并不会触发 类加载过程。
如下代码执行后,静态代码块中的 “start…” 不会输出。
(2)反射破坏
如下代码所示,反射调用构造方法时,会进行类加载过程(输出 “start…” ),然后构建一个实例。
此时的实例对象是通过 构造方法重新创建的对象。与类加载过程中创建的对象不同。
即 反射对 单例模式造成了破坏。
(3)防止反射破坏(未必会生效)
在构造方法中,判断实例是否已经被创建。
类初始化过程中,会创建一个实例。即使通过反射调用构造方法,也会在实例创建之后再去调用,所以在 构造方法中进行判断,实例存在则会抛出异常。从而防止反射破坏(未必会生效,后续序列化破坏中有提到)。
(1)序列化、反序列化
序列化对象,并再次读取对象时(反序列化),会创建一个新的对象。
注:
序列化就是把实体对象状态按照一定的格式写入到有序字节流。
反序列化就是从有序字节流重建对象,恢复对象状态。
(2)反序列化破坏
如下代码所示,通过反序列化创建了个对象。
从实际代码执行结果看,反序列化仅触发了类加载过程(此时调用了构造函数),反序列化中 newInstance() 未主动触发类的构造函数,所以此处构造方法中的判断 无法防止 反序列化中反射的行为。
即 反序列化对 单例模式造成了破坏。
(3)防止反序列化破坏
通过 readResolve() 可以返回一个实例对象,保证此对象为类加载过程中创建的实例对象,即可防止 反序列化破坏。
(1)克隆破坏
如下代码所示,通过克隆创建了个对象。
调用了 Object 的 clone 方法(native 方法),与反序列化类似,也没有触发 类的构造方法(应该是直接从内存中 copy 了一份)。创建了一个新的对象。
即 克隆对 单例模式造成了破坏。
(2)防止克隆破坏
保证 clone() 方法返回的对象为类加载过程中创建的实例对象,即可防止 克隆破坏。
(1)基本说明
(2)反编译一下 enum 类
(3)防止反射破坏
枚举类型的类,没有无参构造。默认继承 Enum 的有参构造。
(4)防止克隆破坏
枚举类型的类,无法重写 clone() 方法。其父类 Enum 中定义 clone() 方法为 final 类型,不能被子类重写。
(5)防止序列化破坏
序列化返回的是同一个对象,无需定义 readResolve() 方法。其执行的是另一个逻辑。
三、懒汉式
(1)基本说明
(2)代码实现(静态方法)
如下代码所示,只允许通过 “类名.方法名” 的方式获取对象。
(3)这就完了吗?
当然不是了,这样写只是在单线程环境下正常执行。多线程操作下,会出现多个实例。
比如:
线程 A 与线程 B 并发执行到 if (fullSingleton == null),此时两个线程的 fullSingleton 均为 null,则均会进入方法,执行 new 实例化操作,此时便会产生多个实例对象。
如下代码所示,代码执行多次可以发现,两个线程输出的对象并不一致。
此时单例模式被破坏,线程不安全。
(4)代码实现(synchronized 同步方法)
为了保证线程安全,可以使用 synchronized 关键字实现同步。
注:
synchronized 保证同一个时刻,只有一个线程可以执行某个方法或者某个代码块。
如下代码所示,在方法上添加一个 synchronized,代码执行多次可以发现,两个线程输出的对象始终一致。
(5)这就完了吗?
当然不是了,虽然使用 synchronized 保证线程安全,但是这种方式锁粒度太大,可能会导致执行效率低。
(6)代码实现(synchronized 同步代码块)
如下代码所示,为了缩小 synchronized 影响范围,可以在方法内部使用同步代码块的方式实现。
(7)这就完了吗?
当然不是了,这样写又回到了 静态方法中 提到的 线程不安全的问题上了。
比如:
线程 A 与线程 B 并发执行到 if (fullSingleton == null),此时两个线程的 fullSingleton 均为 null,则均会进入方法,遇到 synchronized,同步执行后,仍会执行 new 操作,产生多个实例对象。
此时单例模式被破坏,线程不安全。双重检查可以解决这个问题。
(1)代码实现
如下代码所示,双重检查,在 synchronized 同步代码块 的基础上,再添加一个判断。
(2)这就完了吗?
当然不是了。这样写看上去是保证了线程安全,但是有个细节需要思考一下(指令重排)。
如下所示,反编译一下代码,可以看到实例化操作的相关指令。
(3)代码实现(voliate )
使用 voliate 修饰 变量,禁止指令重排序。
(1)基本说明
静态内部类是一种结合了 饿汉模式、懒汉模式 优点的实现方式。
(2)代码实现
如下代码所示,定义一个静态内部类。
(3)这就完了吗?
当然不是了。反序列化破坏、反射破坏、克隆破坏 的问题同样存在。
解决方式与 饿汉模式的解决方式类似。
(4)防止序列化破坏
重写 readResolve() 方法,返回实例对象。
(5)防止克隆破坏
重写 clone() 方法,返回实例对象。
(6)防止反射破坏
在构造方法中,新增一个判断。
四、举例
(1)部分源码
如下代码所示,就是 饿汉模式的 实现。
Original: https://www.cnblogs.com/l-y-h/p/15578922.html
Author: 累成一条狗
Title: 单例模式也能玩出花
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/612584/
转载文章受原作者版权保护。转载请注明原作者出处!