单例模式

单例模式(Singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点,单例模式是创建型模式。

单例模式分为饿汉式单例懒汉式单例

饿汉式单例:
public class Hungry {
    private final static Hungry hungry = new Hungry();

    private Hungry() {  // 构造器私有
    }

    public static Hungry getInstance(){
        return hungry;
    }
}
  • 饿汉式单例在类被初始化时就已经在内存中创建了对象,可能造成空间资源的浪费
  • 以空间换时间,不存在线程安全问题
懒汉式单例:
public class Lazy {
    private static Lazy single;

    private Lazy() { // 构造器私有
    }

    public static Lazy getInstance(){
        if (single==null){
            single = new Lazy();
        }
        return single;
    }
}
  • 懒汉式单例在方法被调用后才创建对象,以时间换空间,在多线程环境下存在风险
public class Lazy {
    private static Lazy single;

    private Lazy() { // 构造器私有
        System.out.println(Thread.currentThread().getName()+" ok");
    }

    public static Lazy getInstance(){
        if (single==null){
            single = new Lazy();
        }
        return single;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                Lazy.getInstance();
            }).start();
        }
    }
}

测试:
在这里插入图片描述
此时还是创建了5个对象,说明懒汉式单例在多线程下不安全

双重检测(Double Check Lock)懒汉式单例——DCL懒汉式单例
public class Lazy {
    private static Lazy single;

    private Lazy() { // 构造器私有
    }

    // 双重检测锁模式的懒汉式单例——DCL懒汉式
    public static Lazy getInstance(){
        if (single==null){
            synchronized (Lazy.class){
                if (single==null){
                    single = new Lazy();
                }
            }
        }
        return single;
    }
}
  • 双重检测可以保证多线程安全
  • 第一次判断 single==null 是为了避免非必要加锁,提高性能,因为锁的创建和释放会消耗很多资源
真正的DCL懒汉式单例
public class Lazy {
    private static volatile Lazy single; // 加上volatile

    private Lazy() { // 构造器私有
    }

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

getInstance方法中 single = new Lazy() 不是一个原子性操作,创建对象的过程是:
① 在堆内存中开辟内存空间
② 执行构造方法,初始化对象
③ 把这个对象指向内存空间
极端情况,JVM中可能发生指令重排,执行过程变为 1 3 2 ,所以必须在 single 前面加上 volatile,volatile 能保证线程间的可见性,防止指令重排

静态内部类单例
public class Holder {
    private Holder(){
    }

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

    public static Holder getInstance(){
        return InnerClass.single;
    }
}
  • 静态内部类形式的单例可保证线程安全,也能保证单例的唯一性
  • 在需要使用的时候内部类才被初始化,防止了空间资源的浪费
反射暴力破坏单例
public class Lazy {
    private static volatile Lazy single; // 加上volatile

    private Lazy() { // 构造器私有
    }

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

    // 反射暴力破坏单例
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Lazy single1 = Lazy.getInstance();
        Constructor<Lazy> declaredConstructor = Lazy.class.getDeclaredConstructor(null); // 通过反射获得无参构造
        declaredConstructor.setAccessible(true); // 设为可见,无视构造方法的私有性
        Lazy single2 = declaredConstructor.newInstance();
        System.out.println(single1.hashCode());
        System.out.println(single2.hashCode());
    }
}

测试:
在这里插入图片描述
此时可以在构造方法中加锁解决,只修改构造方法为:

private Lazy() { // 构造器私有
    synchronized (Lazy.class){
        if (single!=null){
            throw new RuntimeException("不要使用反射来破坏到单例");
        }
    }
}

测试:
在这里插入图片描述
此时程序抛出异常,保护单例模式不被破坏

但此时若又修改测试方法为:

public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
    Constructor<Lazy> declaredConstructor = Lazy.class.getDeclaredConstructor(null); // 通过反射获得无参构造
    declaredConstructor.setAccessible(true); // 设为可见,无视构造方法的私有性
    Lazy single1 = declaredConstructor.newInstance();
    Lazy single2 = declaredConstructor.newInstance();
    System.out.println(single1.hashCode());
    System.out.println(single2.hashCode());
}

测试:
在这里插入图片描述
单例又再次被破坏,但我们可以再增加一个标志位flag,然后修改构造方法,其他不变:

public class Lazy {
    private static volatile Lazy single; // 加上volatile
    private static boolean flag = false;

    private Lazy() { // 构造器私有
        synchronized (Lazy.class){
            if (flag==false){
                flag = true;
            }else {
                throw new RuntimeException("不要使用反射来破坏到单例");
            }
        }
    }

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

    // 反射暴力破坏单例
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Constructor<Lazy> declaredConstructor = Lazy.class.getDeclaredConstructor(null); // 通过反射获得无参构造
        declaredConstructor.setAccessible(true); // 设为可见,无视构造方法的私有性
        Lazy single1 = declaredConstructor.newInstance();
        Lazy single2 = declaredConstructor.newInstance();
        System.out.println(single1.hashCode());
        System.out.println(single2.hashCode());
    }
}

测试:
在这里插入图片描述
测试结果又报异常,保护单例不被破坏

但我们也可以通过反射来得到flag并修改它的值,从而破坏单例,修改测试方法为:

public static void main(String[] args) throws Exception {
    Constructor<Lazy> declaredConstructor = Lazy.class.getDeclaredConstructor(null); // 通过反射获得无参构造
    declaredConstructor.setAccessible(true); // 设为可见,无视构造方法的私有性

    Lazy single1 = declaredConstructor.newInstance();
    Field flag = Lazy.class.getDeclaredField("flag"); // 通过反射获得flag属性
    flag.set(single1,false); // 把flag的值改为false

    Lazy single2 = declaredConstructor.newInstance();
    System.out.println(single1.hashCode());     		
    System.out.println(single2.hashCode());
}

测试:
在这里插入图片描述
这说明在反射下,上面的这些单例模式都是不安全的!所以我们可以使用枚举实现单例!

枚举单例
public enum EnumSingle {
    SINGLE;

    public EnumSingle getInstance(){
        return SINGLE;
    }
}
  • 枚举本身实现了单例
  • 反射不能破坏枚举

反射的newInstance()方法写死了不能破坏枚举
在这里插入图片描述
参考:
https://blog.csdn.net/as513385/article/details/110082439.

https://blog.csdn.net/mnb65482/article/details/80458571?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.baidujs&dist_request_id=1619618168424_01105&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.baidujs.

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值