1、单例模式

文章详细介绍了Java中实现单例模式的四种常见方法:饿汉式、懒汉式(包括简单懒汉式和上锁的懒汉式,后者通过双重检查锁解决线程安全问题)、静态内部类实现以及枚举实现。每种方式都有其优缺点,如饿汉式保证了线程安全但可能造成资源浪费,而枚举实现提供了最高的安全性,但并非绝对安全,多类加载器环境会破坏单例。
摘要由CSDN通过智能技术生成

单例:保证一个类只有一个实例,并提供一个访问该实例的全局访问点

1、饿汉式

存在的问题:如果单例类存在需要开辟大量存储空间的属性,会造成空间浪费

class HungryMan{

    private HungryMan(){}

    private static final HungryMan HUNGRY = new HungryMan();

    public static HungryMan getInstance(){
        return HUNGRY;
    }

}

2、懒汉式

2.1、简单的懒汉式

存在的问题:多线程下极易破坏单例模式原则,创建出多个实例

class LazyMan{

    private LazyMan(){}

    private static LazyMan lazyMan;

    public static LazyMan getInstance(){
        if(null == lazyMan){
            lazyMan = new LazyMan();
        }
        return lazyMan;
    }
}
  • 测试
// 修改单例构造让其输出线程名
private LazyMan(){
    System.out.println(Thread.currentThread().getName() + "===========================");
}

// 测试代码
for(int i = 0; i < 10; i++){
    new Thread(()-> LazyMan.getInstance()).start();
}
  • 其中的一次破坏单例原则的测试结果如下:

在这里插入图片描述

2.2、上锁的懒汉式

  • 双重检测锁模式,简称DCL
class LazyMan{

    private LazyMan(){}

    private static volatile LazyMan lazyMan;

    public static LazyMan getInstance(){
        if(null == lazyMan){
            synchronized (LazyMan.class){
                if(null == lazyMan){
                    lazyMan = new LazyMan();
                }
            }
        }
        return lazyMan;
    }
}

老版本JDK不用volatile关键字可能出现的问题:lazyMan = new LazyMan()不是一个原子操作,它需要经历了以下3步

    1. 分配内存空间
    2. 执行构造,初始化对象
    3. 对象指向开辟的存储空间

在执行这3步的时候可能会发生一个指令重排的现象,正常执行1、2、3,实际却可能是1、3、2。

如果A线程执行的时候走了1、3、2,在A线程走完第2步时,线程B恰好来获取单例对象,此时这个对象已经开辟空间,此时null == lazyMan就是false,B线程就获得了一个‘虚空’对象(我随便取的名)。

为了避免指令重排,需要在声明属性时加上 volatile 关键字

【注:高版本(>1.4)的 Java 已在 JDK 内部解决了这个问题,所以高版本的 Java 不需要关注这个问题】

  • 拓展:使用 volatile 关键字避免指令重排

2.3、静态内部类实现单例

public class Holder {

    private Holder(){}

    public static Holder getInstance(){
        return InnerClass.HOLDER;
    }

    public static class InnerClass{
        private static final Holder HOLDER = new Holder();
    }
}

单例并不安全,因为有反射的存在。枚举实现单例的安全性更高(不是绝对安全的,多个类加载器会破坏枚举实现的单例)

更多内容可参考 单例模式的五种实现方式及优缺点

2.4、枚举实现单例

class EnumSingleton{

    private EnumSingleton(){}
    
    public static EnumSingleton getInstance(){
        return Singleton.INSTANCE.getInstance();
    }

    private static enum Singleton{
    
        INSTANCE;
        
        private EnumSingleton singleton;
        
        //JVM会保证此方法绝对只调用一次
        private Singleton(){
            singleton = new EnumSingleton();
        }
        
        public EnumSingleton getInstance(){
            return singleton;
        }
    }
}
  • 枚举为什么会安全?
// 简单枚举单例
public enum EnumSingle {

    INSTSNCE;

    public EnumSingle getInstance(){
        return INSTSNCE;
    }
}

// 测试代码
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class, int.class);
declaredConstructor.setAccessible(true);
EnumSingle enumSingle = declaredConstructor.newInstance(); // 是这句报错了!
EnumSingle instsnce2 = enumSingle.INSTSNCE;
  • 测试结果:Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
    在这里插入图片描述

为什么枚举构造有两个参数?
编译后的class文件明明是无参构造,但是直接调无参却显示没有这个方法,经过专业反编译软件反编译后发现其实是有两个参数的
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

纯纯的小白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值