- 单例模式
- 一、饿汉式
- 二、懒汉式
- Ⅰ:双层检测锁模式(DCL)
- 三、静态内部类(扩充)
- 四、反射破坏和枚举
- Ⅰ:反射破坏单例模式
- Ⅱ:枚举
- 单例模式可分为:饿汉式 、DCL懒汉式
一、饿汉式注意: 构造器私有化是单例模式的一个重要思想。
public class Hungry {
//单例模式 构造私有化,防止创建
private Hungry(){}
//创建对象信息(饿汉式:开始即创建)
private final static Hungry hungry = new Hungry();
//返回对象信息
public static Hungry getInstance(){
return hungry;
}
}
二、懒汉式
- 缺点:饿汉式单例模式初始化创建就会加载所有的资源,所以可能会产生垃圾对象,浪费空间。
- 优化:在使用时再进行创建加载,所以引出懒汉式单例模式。
public class Lazy {
// 构造器私有化
private Lazy(){}
// 声明对象
private static Lazy lazy;
// 当lazy为null时,创建并返回lazy实例
public static Lazy getInstance(){
if(lazy ==null){
lazy = new Lazy();
}
return lazy;
}
}
Ⅰ:双层检测锁模式(DCL)
- 上述代码实现了在使用时创建对象的能力,相比于饿汉式,在实现上得到了优化。
- 缺点:这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作
public class Lazy {
// 构造器私有化
private Lazy(){
System.out.println(Thread.currentThread().getName() + "ok");
}
// 声明对象
private static Lazy lazy;
// 双重检测锁模式的 懒汉式单例 DCL懒汉式
public static Lazy getInstance(){
if (lazy==null) {
synchronized (Lazy.class){
if(lazy ==null){
lazy = new Lazy();
}
}
}
return lazy;
}
// 主方法起10个线程调用
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
Lazy.getInstance();
}).start();
}
}
}
- 上述代码表面上是可以在多线程的场景下使用,但是还是存在一定的问题。
- 当new Lazy()时,看似是一步操作,其实new的过程并不是一个原子操作。步骤如下:
- 创建对象首先由内存创建空间。
- 执行构造方法,初始化对象。
- 将对象指向创建空间。
- 创建对象过程可能会出现指令重排现象,比如我们期望是按从1–>2–>3的步骤执行,但是真实情况下可能会执行1–>3–>2。会先将对象指向空间,再去初始化对象。这样在第一个线程执行完毕之后,第二个线程执行时,lazy并不为null,这样就会返回一个lazy,但此时lazy并没有完成构造。
- 优化: 为了安全,则需要保证lazy是避免指令重排的,所以要加上volatile关键字修饰lazy。
public class Lazy {
// 构造器私有化
private Lazy(){
System.out.println(Thread.currentThread().getName() + "ok");
}
// 声明对象
private volatile static Lazy lazy;
// 双重检测锁模式的 懒汉式单例 DCL懒汉式
public static Lazy getInstance(){
if (lazy==null) {
synchronized (Lazy.class){
if(lazy ==null){
lazy = new Lazy();
}
}
}
return lazy;
}
// 主方法起10个线程调用
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
Lazy.getInstance();
}).start();
}
}
}
三、静态内部类(扩充)优点: 这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
public class InnerSingle {
private InnerSingle(){}
public static class InnerClass{
private static final InnerSingle innerSingle = new InnerSingle();
}
public static InnerSingle getInstance(){
return InnerClass.innerSingle;
}
}
描述: 这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
思考: 上述三种方式都实现了单例模式,最优解目前看来就是懒汉式的双重检测锁模式实现的单例模式,那么问题来了,就算是懒汉式的双重检测锁模式的单例模式,是否就一定是完美的呢?
四、反射破坏和枚举 Ⅰ:反射破坏单例模式对于上述思考,我们就要考虑到java的反射是否可以破坏我们所写的单例模式,下面我们来探究一下。
- 我们在懒汉式的单例基础上,将主方法改为直接获取实例和通过反射来获取实例相对比,如下:
public static void main(String[] args) throws Exception {
Lazy instance = Lazy.getInstance();
// 使用反射获取空参构造器
Constructor constructor = Lazy.class.getDeclaredConstructor(null);
// 无视私有构造器
constructor.setAccessible(true);
// 通过反射创建实例
Lazy instance1 = constructor.newInstance();
System.out.println(instance);
System.out.println(instance1);
}
- 运行结果:
com.studio.single.Lazy@1b6d3586
com.studio.single.Lazy@4554617c
Process finished with exit code 0
- 可以很明显的看出来,此时两个实例并不相等,这无疑是反射破坏了单例模式。既然反射是通过构造器来创建,那么我们就试一下在构造器内再加上一把锁。如下:
// 构造器私有化
private Lazy(){
synchronized (Lazy.class){
if(lazy != null){
throw new RuntimeException("不要使用反射破坏单例");
}
}
}
- 运行结果:
Exception in thread “main” java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.studio.single.Lazy.main(Lazy.java:41)
Caused by: java.lang.RuntimeException: 不要使用反射破坏单例
at com.studio.single.Lazy.(Lazy.java:17)
… 5 more
Process finished with exit code 1
- 在构造器上加锁,看起来是已经阻止了反射的破坏,但是我们的第一个实例还是通过单例来获取的,如果所有实例均使用反射来创建呢?构造器中的实例存在判定就不会存在,我们继续验证:
public static void main(String[] args) throws Exception {
// Lazy instance = Lazy.getInstance();
// 使用反射获取空参构造器
Constructor constructor = Lazy.class.getDeclaredConstructor(null);
// 无视私有构造器
constructor.setAccessible(true);
// 通过反射创建实例
Lazy instance = constructor.newInstance();
Lazy instance1 = constructor.newInstance();
System.out.println(instance);
System.out.println(instance1);
}
- 运行结果:
com.studio.single.Lazy@1b6d3586
com.studio.single.Lazy@4554617c
Process finished with exit code 0
验证结论: 单例模式又被破坏了
- 此时我们在考虑通过一个静态变量来设置门禁,修改代码如下:
private static boolean flag = false;
// 构造器私有化
private Lazy(){
synchronized (Lazy.class){
if(flag == false){
flag = true;
}else{
throw new RuntimeException("不要使用反射破坏单例");
}
}
}
- 运行结果:
Exception in thread “main” java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.studio.single.Lazy.main(Lazy.java:45)
Caused by: java.lang.RuntimeException: 不要使用反射破坏单例
at com.studio.single.Lazy.(Lazy.java:20)
… 5 more
Process finished with exit code 1
验证结论: 此时我们又一次成功方式了反射对单例的破坏,但是既然是类内的常量,那么就能通过反射来进行变更,我们仍然不能阻止反射破坏单例。
public static void main(String[] args) throws Exception {
// 反射获取常量
Field flag = Lazy.class.getDeclaredField("flag");
flag.setAccessible(true);
// 使用反射获取空参构造器
Constructor constructor = Lazy.class.getDeclaredConstructor(null);
// 无视私有构造器
constructor.setAccessible(true);
// 通过反射创建实例
Lazy instance = constructor.newInstance();
// 在第一次创建实例后,flag重新设置为false
flag.set(instance,false);
Lazy instance1 = constructor.newInstance();
System.out.println(instance);
System.out.println(instance1);
}
Ⅱ:枚举
- 枚举自带单例模式,且反射无法破坏枚举
说明: 枚举也是一个class类,只是继承了Enum
public enum EnumSingle {
INSTANCE;
public static EnumSingle getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) {
EnumSingle instance1 = EnumSingle.getInstance();
EnumSingle instance2 = EnumSingle.getInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
- 验证结论:两个实例是相等的
- 使用反射创建实例:
class Test{
public static void main(String[] args) throws Exception {
EnumSingle instance1 = EnumSingle.getInstance();
Constructor declaredConstructor = EnumSingle.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
EnumSingle instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
- 验证结论:虽然抛出错误,无法对单例造成破坏,但错误为枚举类没有空参构造器。而我们的期望错误是:
throw new IllegalArgumentException(“Cannot reflectively create enum objects”);
- 通过反编译工具,我们可以反向生成java文件,此时我们能够看到构造器代码如下:
private EnumSingle(String s,int i){
super(s,i);
}
- 构造器有两个参数,String和int,我们修改一下测试代码:
class Test{
public static void main(String[] args) throws Exception {
EnumSingle instance1 = EnumSingle.getInstance();
Constructor declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
declaredConstructor.setAccessible(true);
EnumSingle instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
- 验证结论:虽然抛出错误,得到期望验证错误
Exception in thread “main” java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
at com.studio.single.Test.main(EnumSingle.java:23)
- 总结: 这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。



