单例模式(懒汉式和饿汉式)

本文详细介绍了Java中的单例模式,包括饿汉式、懒汉式、DCL懒汉式和静态内部类饿汉式的实现。讨论了线程安全问题以及如何通过volatile关键字和枚举来解决。同时,揭示了反射可能破坏单例模式,并提出了相应的防御措施。最后,展示了枚举单例模式的不可破坏性。
摘要由CSDN通过智能技术生成

什么是单例模式?

单例模式(Singleton Pattern)是Java中最简单的设计模式之一。

它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

饿汉式单例模式

这个模式在类被加载的时候就会实例化一个对象

public class Hungry{
    private Hungry(){
        
    }
    private final static Hungry hungry = new Hungry();
    
    public static Hungry getInstance(){
        return hungry;
    }     
}

饿汉式是最简单的单例模式的写法,保证了线程的安全。

优点:简单快速

缺点:直接初始化,消耗性能

普通懒汉式

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

但是在并发的环境下,单例会失效,所以需要加锁!

DCL懒汉式

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

优点:保证了线程的安全性,符合懒加载,只有在用到的时候,才会去初始化,效率也较高

但是这个单例模式是有一定的问题的:

问题

lazyMan = new LazyMan();

这个语句不是原子性操作,至少经过三个步骤:

1、分配对象内存空间

2、执行构造方法初始化对象

3、设置instance指向刚分配的内存空间,此时instance!=null

由于指令重排,当A线程执行lazyMan = new LazyMan();时,可能先执行了第三步(没有执行第二步),此时线程B进来了,发现lazyMan不为空,直接返回了,并且后面使用了返回的lazyMan,由于线程A都还没有初始化对象,导致现在的lazyMan不完整,可能会有意想不到的错误,所以就需要用到volatile关键字来避免指令重排:

DCL+volatile单例模式

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

静态内部类饿汉式(改进)

public class Holder{
    private Holder(){
        
    }
    
    public static Holder getInstance(){
        return InnerClass.holder;
    }
    
    private static class InnerClass{
        private static final Holder holder = new Holder();
    }
}

优点:保证了线程安全性,同时满足了懒加载

反射对单例模式的破坏

反射可以无视private修饰的构造方法,可以直接在外面创建多个单例对象,把我们的单例模式破坏掉

public static void main(String[] args) {

    try {
        LazyMan lazyMan1  = LazyMan.getInstance();
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        LazyMan lazyMan2 = declaredConstructor.newInstance();
        System.out.println(lazyMan1==lazyMan2);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

可以看到我们的测试结果,创建的两个lazyMan是不相等

在这里插入图片描述

解决方案

在构造方法中加一个class锁,判断如果lazyMan不为空,则直接抛出异常

public class LazyMan{
    private LazyMan(){
        synchronized (LazyMan.class){
            if(lazyMan!=null){
                throw new RuntimeException("不要试图破坏我的单例模式!");
            }
        }
    }

    private volatile static LazyMan lazyMan;

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

在这里插入图片描述

还有问题!

如果一开始就用反射创建两个对象,还是可以创建两个不同的对象!

public static void main(String[] args) {

    try {
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        LazyMan lazyMan1 = declaredConstructor.newInstance();
        LazyMan lazyMan2 = declaredConstructor.newInstance();
        System.out.println(lazyMan1==lazyMan2);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

在这里插入图片描述

继续解决方案!

定义一个boolean变量flag,初始值为false,私有构造函数里面做了一个判断,如果flag==false,就改为true;如果flag等于true,就抛出异常

public class LazyMan{
    private static boolean flag = false;
    private LazyMan(){
        synchronized (LazyMan.class){
            if(flag==false){
                flag = true;
            }else{
                throw new RuntimeException("不要破坏我的单例模式!");
            }
        }
    }

    private volatile static LazyMan lazyMan;

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

在这里插入图片描述

但是还是不能完全防止反射破坏单例模式,因为可以利用反射修改flag的值!

public static void main(String[] args) {

    try {
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        Field field = LazyMan.class.getDeclaredField("flag");
        field.setAccessible(true);

        declaredConstructor.setAccessible(true);
        LazyMan lazyMan1 = declaredConstructor.newInstance();

        field.set(lazyMan1,false);
        LazyMan lazyMan2 = declaredConstructor.newInstance();
        System.out.println(lazyMan1==lazyMan2);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

在这里插入图片描述

所以我们需要用到枚举!

枚举

public enum  enumSingleton {
    INSTANCE;
    public enumSingleton getInstance(){
        return INSTANCE;
    }
}
public class demo02 {
    public static void main(String[] args) {
        enumSingleton singleton1 = enumSingleton.INSTANCE;
        enumSingleton singleton2 = enumSingleton.INSTANCE;
        System.out.println(singleton1==singleton2);
    }
}

在这里插入图片描述

创建的类是一样的!

如果我们使用反射去破坏,首先需要知道,枚举在IDEA中的源码

public enum enumSingleton {
    INSTANCE;

    private enumSingleton() {
    }

    public enumSingleton getInstance() {
        return INSTANCE;
    }
}

显示是无参构造,但其实如果我们使用jad进行反编译可以看到枚举的构造方法是有参数的!

private enumSingleton(String s,int i){
    super(s,i);
}

所以开始破坏!

public static void main(String[] args) {
        try {
            Constructor<enumSingleton> declaredConstructor = enumSingleton.class.getDeclaredConstructor(String.class, int.class);
            declaredConstructor.setAccessible(true);
            enumSingleton singleton2 = declaredConstructor.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

在这里插入图片描述

不能破坏,显示不能反射创建!

ss.getDeclaredConstructor(String.class, int.class);
            declaredConstructor.setAccessible(true);
            enumSingleton singleton2 = declaredConstructor.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

在这里插入图片描述

不能破坏,显示不能反射创建!

本文章是通过观看bilibili狂神说视频整理出来的!狂神老师讲的很好,希望大家可以去看看!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值