• java设计模式-单例模式


    定义

    饿汉式

    public class Hungry {
    
        public final static Hungry INSTANCE = new Hungry();
    
        private Hungry() {
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    饿汉式静态代码块

    public class HungryStatic {
    
    
        public final static HungryStatic INSTANCE;
    
        static {
            INSTANCE = new HungryStatic("例如从配置文件读入");
        }
    
        private HungryStatic(String config) {
        }
    
        private String config;
    
        public String getConfig() {
            return config;
        }
    
        public void setConfig(String config) {
            this.config = config;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    枚举

    public enum Enum {
        
        INSTANCE;
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    懒汉式 (线程不安全)

    在调用的时候才创建

    public class Lazy {
    
        private static Lazy instance;
    
        private Lazy() {
        }
    
        public static Lazy getInstance() {
            if (instance == null) {
                instance = new Lazy();
            }
            return instance;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    懒汉式

    public class LazySafe {
    
        private static volatile LazySafe instance;
    
        private LazySafe() {
        }
    
        public static LazySafe getInstance() {
            if (instance == null) {
                synchronized (LazySafe.class) {
                    if (instance == null) {
                        instance = new LazySafe();
                    }
                }
            }
            return instance;
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    为什么需要第二次判断

    因为 如果两个线程 同时判断 instance == null ,开始争抢锁,第一个线程执行过后,如果不加锁,线程二会直接new 一个新对象,所以必须加锁保证 。

    为什么要加 volatile

    jvm 会将 new 指令解析为下面的语句。

    在这里插入图片描述
    由于指令会重排序,所以 7 有可能 发生在 4之前,也就是 先分配指针指向对象,但是对象尚未初始化也就是null的情况,所以添加关键字禁止重排序情况的发生。

    懒汉式 静态内部类

    原理 : 静态内部类的延迟初始化,且类只会加载一次。

    public class LazyInner {
    
        private LazyInner() {
        }
    
        private static class Inner {
            private static final LazyInner INSTANCE = new LazyInner();
        }
    
        public static LazyInner getInstance() {
            return Inner.INSTANCE;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    The “Double-Checked Locking is Broken” Declaration

    破坏单例

    多线程破坏单例

    懒汉式创建

    指令重排序破坏单例

    在 new 对象的过程中,如果不添加 volatile 关键字,可能会出现重排序,也就是 对象初始化发生在 引用指向 之前,在这一段时间内,其他线程会拿到为null的对象。

    克隆破坏单例

    反序列化破坏单例

    反射破坏单例

    破坏实例

    我们以Runtime为例来演示使用反射破坏单例。

      		Class<Runtime> aClass = Runtime.class;
            Constructor<Runtime> cons = aClass.getDeclaredConstructor();
            cons.setAccessible(true);
            Runtime runtime = cons.newInstance();
            Runtime runtime2 = cons.newInstance();
            System.out.println(runtime);
            System.out.println(runtime2);
            ----
    java.lang.Runtime@568db2f2
    java.lang.Runtime@378bf509
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    解决方案

    优先使用枚举来解决反射破坏单例的问题

    在这里插入图片描述
    我们来获取一下枚举类中的构造器

            Class<Enum> aClass = Enum.class;
            Constructor<?>[] constructors = aClass.getDeclaredConstructors();
            // Constructor<Enum> cons = aClass.getDeclaredConstructor();
            Constructor<Enum> cons2 = aClass.getDeclaredConstructor(String.class, int.class);
            // cons.setAccessible(true);
            cons2.setAccessible(true);
            // Enum e1 = cons.newInstance();
            // Enum e2 = cons.newInstance();
            Enum e3 = cons2.newInstance();
            Enum e4 = cons2.newInstance();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    可以看到里面会生成一个String , int 参数类型的构造器
    在这里插入图片描述
    idea 反编译结果跟预期明显不同。
    在这里插入图片描述
    javap 反编译 class 文件后发现也没有相应的构造方法
    在这里插入图片描述
    我们更换反编译工具,使用 jad 进行后续的测试

    jad下载地址

    在这里插入图片描述
    将 jad.exe 放在 jdk 的 bin 目录下即可
    在这里插入图片描述

    jad 反编译后成功看到Enum类中 String 和 int 参数的构造函数。

    在这里插入图片描述

    应用场景

    Runtime类是 饿汉式 单例

    在这里插入图片描述

  • 相关阅读:
    阿里云CentOS 安装 Nginx
    mybatis获取参数的两种方式: #{ } 和 ${ }
    javaweb-SMBMS
    Django基础理论整理
    Spring IoC有什么好处呢?
    钢筋智能测径仪 光圆与带肋钢筋均可检测!
    语言大模型的浮点运算分配
    Prometheus详解(十)——Prometheus容器监控
    新能源车后市场:华胜、蔚来、途虎严阵以待
    Leetcode 剑指 Offer II 049. 求根节点到叶节点数字之和
  • 原文地址:https://blog.csdn.net/qq_37151886/article/details/125598788