单例模式的4种实现方式

简介: 单例模式的4种实现方式

1:前言

  • 单例模式:保证一个类只有一个实例,并且提供一个全局访问点
  • 使用场景:重量级的对象,不需要多个实例,如线程池,数据库连接池

2:懒汉模式

延迟加载, 只有在真正使用的时候,才开始实例化。

  1. 存在的问题:
  2. 线程安全问题
  3. double check 加锁优化
  4. 编译器 (JIT) , CPU 有可能对指令进行重排序,导致使用到尚未初始化 的实例,可以通过添加volatile 关键字进行修饰, 对于volatile 修饰的字段,可以防止指令重排。
public class LazySingleton {
    // 禁止指令重排
    private volatile static LazySingleton instance;
    private LazySingleton(){}
    public static LazySingleton getInstance(){
        if(instance == null){
            synchronized (LazySingleton.class){
                if(instance == null){
                    instance = new LazySingleton();
                    // 字节码层
                    // JIT , CPU 有可能对如下指令进行重排序
                    // 1 .分配空间
                    // 2 .初始化
                    // 3 .引用赋值
                    // 如重排序后的结果为如
                    // 1 .分配空间
                    // 3 .引用赋值 如果在当前指令执行完,有其他线程来获取实例,将拿到尚未初始化好的实例
                    // 2 .初始化
                }
            }
        }
        return instance;
    }
}

2.1测试

public class LazySingletonTest {
    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            LazySingleton instance = LazySingleton.getInstance();
            System.out.println(instance);
        });
        Thread t2 = new Thread(()->{
            LazySingleton instance = LazySingleton.getInstance();
            System.out.println(instance);
        });
        t1.start();
        t2.start();
    }
}

打印结果如下:

org.hzz.singleton.v1.LazySingleton@4a10f8eb
org.hzz.singleton.v1.LazySingleton@4a10f8eb

3:饿汉模式

类加载的 初始化阶段就完成了 实例的初始化 。本质上就是借助于jvm 类加载机制,保证实例的唯一性(初始化过程只会执行一次)及线程安 全(**JVM以同步的形式来完成类加载的整个过程**)。

3.1 类加载过程:

  • 加载二进制数据到内存中, 生成对应的Class数据结构
  • 连接: a. 验证, b.准备(给类的静态成员变量赋默认值),c.解析
  • 初始化: 给类的静态变量赋初值

只有在真正使用对应的类时,才会触发初始化 如( 当前类是启动类即 main函数所在类,直接进行new 操作,访问静态属性、访问静态方 法,用反射访问类,初始化一个类的子类等.)

public class HungrySingleton {
    private final static HungrySingleton instance = new HungrySingleton();
    public HungrySingleton(){
        System.out.println("HungrySingleton 实例化");
    }
    public static HungrySingleton getInstance(){
        return instance;
    }
}

3.2 测试

public class HungrySingletonTest {
    public static void main(String[] args) {
        HungrySingleton instance1 = HungrySingleton.getInstance();
        HungrySingleton instance2 = HungrySingleton.getInstance();
        System.out.println(instance2 == instance1);
    }
}
/**
 * true
 */

4:静态内部类

本质上是利用类的加载机制来保证线程安全

只有在实际使用的时候,才会触发类的初始化,所以也是懒加载的一 种形式。

public class InnerClassSingleton {
    private static class InnerClassHolder{
        static {
            System.out.println("InnerClassHolder 初始化");
        }
        private static InnerClassSingleton instance = new InnerClassSingleton();
    }
    //构造器私有化
    private InnerClassSingleton(){}
    public static InnerClassSingleton getInstance(){
        return InnerClassHolder.instance;
    }
}

4.1 反射攻击

public class ReflectAtack {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Constructor<InnerClassSingleton> constructor =                            InnerClassSingleton.class.getDeclaredConstructor();
        //越过构造器私有化
        constructor.setAccessible(true);
        // 通过发射InnerClassHolder的static静态代码块没有执行
        InnerClassSingleton innerClassSingleton = constructor.newInstance();
        InnerClassSingleton instance = InnerClassSingleton.getInstance();
        System.out.println(innerClassSingleton == instance); // false
    }
}

4.2 静态内部类防止反射破坏

public class InnerClassSingleton {
    private static class InnerClassHolder{
        static {
            System.out.println("InnerClassHolder 初始化");
        }
        private static InnerClassSingleton instance = new InnerClassSingleton();
    }
    private InnerClassSingleton(){
        //当通过反射攻击走到这一步
        if(InnerClassHolder.instance == null){  // 有意思
            throw new RuntimeException("单例不允许多个实例");  
        }
    }
    public static InnerClassSingleton getInstance(){
        return InnerClassHolder.instance;
    }
}

5:枚举类型

天然不支持反射创建对应的实例,且有自己的反序列化机制

利用类加载机制保证线程安全

public enum EnumSingleton {
    INSTANCE;
     public void print(){
         System.out.println(this.hashCode());
     }
}



目录
相关文章
|
监控 Java 应用服务中间件
spring和springboot的区别
spring和springboot的区别
899 1
|
8月前
|
算法 数据可视化 Java
Java 规则引擎封神指南:从底层原理到生产落地,零冗余全干货实战
规则引擎解耦业务规则与系统代码,实现规则快速迭代。本文系统讲解规则引擎原理与实战,对比Drools、EasyRules等主流方案,剖析Rete算法核心逻辑。通过电商风控系统实战,展示生产级规则引擎架构,包含规则持久化、动态热更新等关键功能。总结性能优化、规则治理等最佳实践,解答常见问题,帮助开发者掌握规则引擎选型与落地。规则引擎适用于规则频繁变更场景,能将规则迭代周期从天级压缩到分钟级,但需根据业务复杂度合理选用。
2256 2
|
Java 应用服务中间件 Spring
为什么SpringBoot的 jar 可以直接运行?
SpringBoot的 jar 可以直接运行的原因
1277 2
|
前端开发
MVVM是什么?和MVC有何区别呢?
【10月更文挑战第11天】MVVM 和 MVC 都是为了更好地组织和管理软件架构,提高开发效率和代码质量。理解它们的特点和区别,有助于我们在实际开发中做出更合适的选择,并构建出更加优秀的应用程序。
|
自然语言处理 安全 算法
加密算法
【10月更文挑战第6天】加密算法
611 5
|
数据库
什么是接口幂等性?如何保证接口幂等性?
接口幂等性(Idempotency)是指同样的请求被重复执行多次,产生的结果与执行一次的结果相同。换句话说,接口无论被调用一次还是多次,系统的最终状态保持不变。
2969 5
|
设计模式 安全 Java
Java编程中的单例模式实现与应用
【8月更文挑战第31天】在Java的世界里,单例模式是构建高效且资源友好应用的基石之一。本文将深入浅出地介绍如何通过单例模式确保类只有一个实例,并提供一个全局访问点。我们将探索多种实现方法,包括懒汉式、饿汉式和双重校验锁,同时也会讨论单例模式在多线程环境下的表现。无论你是Java新手还是资深开发者,这篇文章都将为你打开一扇理解并有效应用单例模式的大门。
|
XML Java 数据库连接
【Java】ruoyi框架,添加ShardingJdbc支持分库分表
【Java】ruoyi框架,添加ShardingJdbc支持分库分表
1846 0

热门文章

最新文章