2022.8.21 深入单例模式

18、深入单例模式

java中单例模式是一种常见的设计模式,单例模式的写法有好几种,这里主要介绍三种:懒汉式单例、饿汉式单例、登记式单例。

  单例模式有以下特点:

  1、单例类只能有一个实例。

  2、单例类必须自己创建自己的唯一实例。

  3、单例类必须给所有其他对象提供这一实例。

  单例模式确保某个类只有一个实例(构造方法私有且有一个当前类的静态成员变量),而且自行实例化并向整个系统提供这个实例(有一个静态方法)。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。

饿汉式

复制代码
 1  package com.xing.single;
 2  //饿汉式单例
 3  public class Hungry {
 4      
 5      //可能会浪费空间
 6      //把所有的对象全部加载进来
 7      private  byte[] date1 = new byte[1024*1024];
 8      private  byte[] date2 = new byte[1024*1024];
 9      private  byte[] date3 = new byte[1024*1024];
10      private  byte[] date4 = new byte[1024*1024];
11 12      private Hungry(){
13 14      }
15      //在类创建的时候就已经创建好了一个静态对象提供使用
16      private final static Hungry HUNGRY = new Hungry();
17 18      public static Hungry getInstance(){
19          return HUNGRY;
20      }
21  }
复制代码

总结:一开始就把对象创建好,然后返回对象给调用方法的人,缺点浪费空间,优点,一开始用final写死了对象,对象怎么样都不会出现新的。

懒汉式

复制代码
 1  package com.xing.single;
 2  3  //懒汉式单例模式  DCL
 4  public class LazyMan {
 5      private LazyMan() {
 6          System.out.println(Thread.currentThread().getName() + "ok" );
 7      }
 8      private volatile static LazyMan lazyMan;//lazyMan必须加上volatile防止指令重排
 9      public static LazyMan getInstance(){
10          //加锁
11          //双重检测锁模式
12          if (lazyMan == null){
13              synchronized (LazyMan.class){//只有在空的时候才开始抢锁
14                  if  (lazyMan == null) {
15                       lazyMan = new LazyMan();//不是一个原子性操作  分为以下3步
16                      /**
17                       * 1.分配内存空间
18                       * 2.执行构造方法,初始化对象
19                       * 3.把这个对象指向这个空间
20                       *
21                       * 期望顺序是:123  可能指令重排
22                       * 特殊情况下实际执行:132  ===>  此时 A 线程没有问题
23                       *                              若额外加一个 B 线程,在A完成3但没有完成2时,因为已经指向空间,B会认为lazyMan != null
24                       *                              此时lazyMan还没有完成构造
25                       */
26                  }
27              }
28          }
29          return lazyMan;
30      }
31      //单线程下是可以的
32      //多线程并发,线程启动偶尔成功,偶尔失败
33      public static void main(String[] args) {
34          for (int i = 0; i < 10; i++) {
35              new Thread(() -> {
36                  LazyMan.getInstance();
37              }).start(); 
38          }
39      }
40  }
复制代码

总结:优点有需求才创建,节省空间,缺点多线程下会出现问题,再获取对象时候,加上判断,如果LazyMan为空则加上类锁,这也就能防止多个线程同时去创建LazyMan。但是还是有缺陷,如果我们此时用反射获取对象,则会出现问题

使用反射破坏单例模式

复制代码
 1  package com.xing.single;
 2  3  import java.lang.reflect.Constructor;
 4  5  public class LazyMan {
 6      private volatile static LazyMan lazyMan;
 7      //空参构造器
 8      private LazyMan() {
 9          System.out.println(Thread.currentThread().getName()+" " + "ok");
10      }
11 12      public static LazyMan getInstance() {
13          if (lazyMan == null) {
14              synchronized (LazyMan.class) {
15                  if (lazyMan == null) {
16                      lazyMan = new LazyMan();
17                      System.out.println(lazyMan);
18                  }
19              }
20          }
21          return lazyMan;
22      }
23 24      //反射
25      public static void main(String[] args) throws Exception {
26          LazyMan instance = LazyMan.getInstance();
27          Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);//获取LazyMan的空参构造器
28          declaredConstructor.setAccessible(true);//取消私有化
29          LazyMan instance2 = declaredConstructor.newInstance();//通过反射创建对象
30 31          System.out.println(instance);
32          System.out.println(instance2);
33      }
34  }
35 36
复制代码

小结:发现这里的两个值已经不是同一个了,按照单列模式应该一样才对,说明反射能够破坏这种单列

为了保持单列模式获取的值需要一样,在空参的地方添加异常,以便接下来的判断(重)

解决方法:锁住对象模板

复制代码
 1  package com.xing.single;
 2  3  import java.lang.reflect.Constructor;
 4  5  public class LazyMan {
 6      private volatile static LazyMan lazyMan;
 7  8      //锁住LazyMan.class
 9      public LazyMan() {
10          synchronized (LazyMan.class){
11              if (LazyMan.class != null){
12                  throw  new RuntimeException("不要试图使用反射构造异常");
13              } 
14          }
15      }
16 17 18      public static LazyMan getInstance() {
19          if (lazyMan == null) {
20              synchronized (LazyMan.class) {
21                  if (lazyMan == null) {
22                      lazyMan = new LazyMan();
23                      System.out.println(lazyMan);
24                  }
25              }
26          }
27          return lazyMan;
28      }
29 30      //反射
31      public static void main(String[] args) throws Exception {
32          LazyMan instance = LazyMan.getInstance();
33          Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);//获取LazyMan的空参构造器
34          declaredConstructor.setAccessible(true);//取消私有化
35          LazyMan instance2 = declaredConstructor.newInstance();//通过反射创建对象
36 37          System.out.println(instance);
38          System.out.println(instance2);
39      }
40  }
41 42
复制代码

 

但是两个都是反射创建的呢?

复制代码
 1  package com.xing.single;
 2  3  import java.lang.reflect.Constructor;
 4  5  public class LazyMan {
 6      
 7      private volatile static LazyMan lazyMan;
 8  9      public LazyMan() {
10          synchronized (LazyMan.class){
11              if (LazyMan.class != null){
12                  throw new RuntimeException("不要试图使用反射构造异常");
13              }
14          }
15      }
16       
17      public static LazyMan getInstance() {
18          if (lazyMan == null) {
19              synchronized (LazyMan.class) {
20                  if (lazyMan == null) {
21                      lazyMan = new LazyMan();
22                      System.out.println(lazyMan);
23                  }
24              }
25          }
26          return lazyMan;
27      }
28 29      //反射
30      public static void main(String[] args) throws Exception {
31          // 不通过getInstance()去创建对象
32          // LazyMan instance = LazyMan.getInstance();
33 34          Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);//获取LazyMan的空参构造器
35          declaredConstructor.setAccessible(true);//取消私有化
36          LazyMan instance = declaredConstructor.newInstance();
37          LazyMan instance2 = declaredConstructor.newInstance();
38          System.out.println(instance);
39          System.out.println(instance2);
40      }
41 42  }
复制代码

 


这里有点问题需要重新测试

解决方法设置标志位

复制代码
 1  package single;
 2  3  import java.lang.reflect.Constructor;
 4  import java.lang.reflect.Field;
 5  import java.lang.reflect.InvocationTargetException;
 6  7  //懒汉式单例模式
 8  public class LazyMan {
 9      private static boolean flag = false;//标志位
10      //解决方法,但是不彻底
11      public LazyMan() {
12          synchronized (LazyMan.class){
13              if (flag == false){
14                  flag = true;
15              }else {
16                  throw new RuntimeException("不要试图使用反射构造异常");
17              }
18 19          }
20          System.out.println(Thread.currentThread().getName() + "ok" );
21      }
22      private volatile static LazyMan lazyMan;//lazyMan必须加上volatile防止指令重排
23      public static LazyMan getInstance(){
24          //加锁
25          //双重检测锁模式
26          if (lazyMan == null){
27              synchronized (LazyMan.class){//只有在空的时候才开始抢锁
28                  if  (lazyMan == null) {
29                      lazyMan = new LazyMan();//不是一个原子性操作
30                      /**
31                       * 1.分配内存空间
32                       * 2.执行构造方法,初始化对象
33                       * 3.把这个对象指向这个空间
34                       *
35                       * 期望顺序是:123
36                       * 特殊情况下实际执行:132  ===>  此时 A 线程没有问题
37                       *                               若额外加一个 B 线程
38                       *                               此时lazyMan还没有完成构造
39                       */
40                  }
41              }
42          }
43 44          return lazyMan;
45      }
46 47 48      public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
49          //反射 破坏了构造器
50          //LazyMan instance = LazyMan.getInstance();
51          Constructor<LazyMan> declaredConstructors = LazyMan.class.getDeclaredConstructor(null);
52          declaredConstructors.setAccessible(true);
53          LazyMan instance = declaredConstructors.newInstance();
54          LazyMan instance2 = declaredConstructors.newInstance();
55          System.out.println(instance);
56          System.out.println(instance2);
57 58 59      }
60  }
61
复制代码

 

通过反射破坏标志位

复制代码
 1  package single;
 2  3  import java.lang.reflect.Constructor;
 4  import java.lang.reflect.Field;
 5  import java.lang.reflect.InvocationTargetException;
 6  7  //懒汉式单例模式
 8  public class LazyMan {
 9      private static boolean flag = false;//标志位
10      //解决方法,但是不彻底
11      public LazyMan() {
12          synchronized (LazyMan.class){
13              if (flag == false){
14                  flag = true;
15              }else {
16                  throw new RuntimeException("不要试图使用反射构造异常");
17              }
18 19          }
20          System.out.println(Thread.currentThread().getName() + "ok" );
21      }
22      private volatile static LazyMan lazyMan;//lazyMan必须加上volatile防止指令重排
23      public static LazyMan getInstance(){
24          //加锁
25          //双重检测锁模式
26          if (lazyMan == null){
27              synchronized (LazyMan.class){//只有在空的时候才开始抢锁
28                  if  (lazyMan == null) {
29                      lazyMan = new LazyMan();//不是一个原子性操作
30 31                  }
32              }
33          }
34 35          return lazyMan;
36      }
37 38 39      public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
40          //反射 破坏了构造器
41          //LazyMan instance = LazyMan.getInstance();
42          Field flag = LazyMan.class.getDeclaredField("flag");//拿到标志位
43          flag.setAccessible(true);//破坏标志位的私有权限
44 45          Constructor<LazyMan> declaredConstructors = LazyMan.class.getDeclaredConstructor(null);
46          declaredConstructors.setAccessible(true);
47          LazyMan instance = declaredConstructors.newInstance();
48 49          flag.set(instance,false);//创建完第一个对象后,在将标志位的值改为false
50          LazyMan instance2 = declaredConstructors.newInstance();
51          System.out.println(instance);
52          System.out.println(instance2);
53 54 55      }
56  }
57
复制代码

 

结果:说明反射的确能解决这个问题,但是无法防止对方反编译去获取我们的标志位从而使用反射改变标志位和反射结果:

所以我们打算去枚举看看说是反射不能破坏枚举的单列

复制代码
 1  package com.xing.single;
 2  3  import java.lang.reflect.Constructor;
 4  import java.lang.reflect.InvocationTargetException;
 5  6  //enum 是一个什么?
 7  //本身可以是class类
 8  public enum Enumsingle {
 9      INSTANCE;
10      public Enumsingle getInstance(){
11          return INSTANCE;
12      }
13 14  }
15  class Test{
16      public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
17          Enumsingle instance = Enumsingle.INSTANCE;
18          Enumsingle instance2 = Enumsingle.INSTANCE;
19          System.out.println(instance);
20          System.out.println(instance2);
21      }
22  }
复制代码

 

输出结果确实两个相同符合单列模式

接下来试试反射:看到枚举用的是空参构造

这里idea说我们这是空参所以我们试试空参

复制代码
 1  package com.xing.single;
 2  3  import java.lang.reflect.Constructor;
 4  import java.lang.reflect.InvocationTargetException;
 5  6  //enum 是一个什么?
 7  //本身可以是class类
 8  public enum Enumsingle {
 9      INSTANCE;
10      public Enumsingle getInstance(){
11          return INSTANCE;
12      }
13 14  }
15  // 测试我这个枚举是否唯一(通过反射尝试破坏)
16  class Test{
17      public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
18          Enumsingle instance = Enumsingle.INSTANCE;
19          Constructor<Enumsingle> declaredConstructor = Enumsingle.class.getDeclaredConstructor(null);
20          declaredConstructor.setAccessible(true);
21          Enumsingle instance2 = declaredConstructor.newInstance();
22          System.out.println(instance);
23          System.out.println(instance2);
24 25          //报错了
26          //NoSuchMethodException: single.Enumsingle.<init>() 没有空参构造方法
27          //IllegalArgumentException: Cannot reflectively create enum objects 不能使用反射破坏
28 29 30      }
31  }
32
复制代码

 

所以我们去反编译看看

这边显示也是空参说明反编译也在骗我们

所以为我们需要使用更专业的工具

这也会生成一个idea文件,打开发现

这边枚举就是一个类只是继承了枚举,这个工具表达了这个参数并不是空参而是String,int的所以我们在Class类加上参数

代码变为:

复制代码
 1  package com.xing.single;
 2  3  import java.lang.reflect.Constructor;
 4  import java.lang.reflect.InvocationTargetException;
 5  6  //enum 是一个什么?
 7  //本身可以是class类
 8  public enum Enumsingle {
 9      INSTANCE;
10      public Enumsingle getInstance(){
11          return INSTANCE;
12      }
13 14  }
15  // 测试我这个枚举是否唯一(通过反射尝试破坏)
16  class Test{
17      public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
18          Enumsingle instance = Enumsingle.INSTANCE;
19          Constructor<Enumsingle> declaredConstructor = Enumsingle.class.getDeclaredConstructor(String.class,int.class);
20          declaredConstructor.setAccessible(true);
21          Enumsingle instance2 = declaredConstructor.newInstance();
22          System.out.println(instance);
23          System.out.println(instance2);
24 25          //报错了
26          //NoSuchMethodException: single.Enumsingle.<init>() 没有空参构造方法
27          //IllegalArgumentException: Cannot reflectively create enum objects 不能使用反射破坏
28 29      }
30  }
31 32
复制代码

 

这个

 

 

这个输出来的异常才是正确的,最后成功解决了这个问题

反射不能破坏枚举单例

posted @   暴躁C语言  阅读(64)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
点击右上角即可分享
微信分享提示