单例模式(饿汉模式、懒汉模式、静态内部类、枚举单例)反射破解

根据狂神说写的笔记:https://www.bilibili.com/video/BV1K54y197iS?from=search&seid=2247731384023907916

一、饿汉式单例(程序一开始就加载)

package com.yyh.danlimoshi;
//饿汉模式,一开始就全部加载
public class HungryMan {

    int[] a=new int[4];
    int[] a1=new int[4];



    //构造器私有
    private HungryMan(){
    }
    private static   HungryMan HUNGRY_MAN =new HungryMan();
    public static HungryMan getInstance(){
        return  HUNGRY_MAN;
    }

}

二、懒汉式单例(用才加载,不用不加载)

package com.yyh.danlimoshi;
//懒汉式  用到的时候才加载
public class LazyMan {
 //构造器私有
    private LazyMan(){
        System.out.println("线程启动"+Thread.currentThread().getName());

    }
private static LazyMan LAZY_MAN=null;
    public  static  LazyMan getIntance(){
        if(LAZY_MAN==null){
            LAZY_MAN=new LazyMan();
        }
        return LAZY_MAN;
    }

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

}


这个程序单线程模式没问题,多线程出现下面问题
在这里插入图片描述
线程只启动了两个,成功的次数每次不一样
解决:加双层锁,锁class,每次进去的线程只有一个

private static LazyMan LAZY_MAN=null;
    public  static  LazyMan getIntance(){
        //加两层锁是为了提高效率,线程进入第一个if语句,假如LazyMan类锁被其他线程占用,则等待,这个等待可以理解为在锁内等待,所以提高效率
        

        if(LAZY_MAN==null){
            synchronized (LazyMan.class){
                if(LAZY_MAN==null){
                    LAZY_MAN=new LazyMan();
                }
            }
        }
        return LAZY_MAN;
    }

                    //DCL懒汉式

以上代码还是会出现问题,因为 LAZY_MAN=new LazyMan()不是原子操作,new的过程中分三步:
1.分配内存空间
2.执行构造方法,初始化对象
3.把初始化的对象指向分配的空间
这个过程可能发生指令重排,比如:
A线程执行过程为132的时候,执行没问题
然后线程B执行的时候因为已经指向了分配的空间,所以判读if(LAZY_MAN==null)不成立,返回空的LAZY_MAN(空指针异常)

解释:

2初始化对象是一个内存操作,这可能是一个耗时操作,为了避免CPU在等待这个操作时停顿导致性能下降,编译器会调整指令顺序(开启优化)或者有些架构的CPU会乱序执行指令,也就是将3提前执行,这样的话,指针就指向了一个未初始化好的对象。外部得到的单例对象是一个未初始化好的对象,就会引发问题,并不是说new对象的时候出错。这只在多线程场景下才会有小概率出现。指令重排和CPU架构、编译器优化有关,和编程语言无关,不同编程语言都提供了一定的API或赋予关键字一定语义来保证CPU不乱序执行或者编译器不执行指令重排优化。
在C/C++中,volatile是为了提醒编译器在执行的单一线程内, volatile 访问不能被优化掉,但是volatile并不能保证数据是多线程安全的。其他语言也应当是一样的,但是不同语言可能赋予了volatile更多的语义

解决办法:加上volatile防止指令重排

private volatile static LazyMan LAZY_MAN=null;

三、静态内部类单例

package com.yyh.danlimoshi;
//静态内部类
public class Holder {
    private Holder(){}

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

    public  static  class Inner{
        public static Holder HOLDER=new Holder();
    }
}

四、以上三个都是不安全的,因为有反射机制,可以全部破解,以懒汉式单例为例(不知道反射的去看反射篇:https://blog.csdn.net/weixin_45653314/article/details/110173952

 /*利用反射机制破解*/
        LazyMan intance = LazyMan.getIntance();
        //利用反射获取一个对象
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        LazyMan instance1 = declaredConstructor.newInstance();
        System.out.println(instance1.equals(intance));

输出结果为:
在这里插入图片描述
解决办法:在私有构造器里面加锁

 private LazyMan(){

        synchronized (LazyMan.class){
            if (LAZY_MAN!=null){
                throw new RuntimeException("不要用反射破环单例");
            }
        }
        System.out.println("线程启动"+Thread.currentThread().getName());

    }

输出结果:
在这里插入图片描述
但是还是有问题,假如把代码换成下面的话,还是可以破坏单例:

  Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        LazyMan instance1 = declaredConstructor.newInstance();
        LazyMan instance = declaredConstructor.newInstance();
        System.out.println(instance1.equals(instance));

输出结果:
在这里插入图片描述

上面这种情况的话可以通过红绿灯解决(打标记),解决办法:

 private static  boolean red=false;
 //构造器私有
    private LazyMan(){

        synchronized (LazyMan.class){
            if(red==false){
                red=true;
            }else {
                throw new RuntimeException("不要用反射破环单例");
            }
        }
        System.out.println("线程启动"+Thread.currentThread().getName());

    }

输出结果:
在这里插入图片描述
即使这样也可以利用反射的机制破坏单例:

 Field red = LazyMan.class.getDeclaredField("red");
        red.setAccessible(true);

        //利用反射获取一个对象
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        LazyMan instance1 = declaredConstructor.newInstance();
        red.set(instance1,false);
        LazyMan instance = declaredConstructor.newInstance();
        System.out.println(instance1.equals(instance));
  

输出结果:
在这里插入图片描述

四、枚举单例

package com.yyh.danlimoshi;

public enum  EnumSigle {
    RED;
    public EnumSigle getInstance(){
        return RED;
    }

    public static void main(String[] args) {
        EnumSigle red = EnumSigle.RED;
        EnumSigle red1 = EnumSigle.RED;
        System.out.println(red==red1);


    }
}

现在尝试用反射机制破坏枚举:

package com.yyh.danlimoshi;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public enum  EnumSigle {
    RED;
    public EnumSigle getInstance(){
        return RED;
    }

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        EnumSigle red = EnumSigle.RED;
        Constructor<EnumSigle> declaredConstructor = EnumSigle.class.getDeclaredConstructor(String.class,int.class);
        declaredConstructor.setAccessible(true);
        EnumSigle enumSigle = declaredConstructor.newInstance();
        System.out.println(red);
        System.out.println(enumSigle);


    }
}

运行结果:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值