单例模式详解

单例模式

1)饿汉式
/**
 * 饿汉式单例
 */
public class Hungry {

    /**
     * 可能会浪费空间
     */
    private Hungry(){

    }
    private final static Hungry hungry = new Hungry();

    public static Hungry getInstance(){
        return hungry;
    }

}
2)DCL懒汉式

懒汉式最基础模型:

class LazyMan{
    private static LazyMan lazyMan;
    private LazyMan(){}
    public static LazyMan getInstrance(){
        if(lazyMan==null){
            lazyMan=new LazyMan();
        }
        return lazyMan;
    }
}
public class Test{
    public static void main(String[] args){
        LazyMan lazyMan=LazyMan.getInstrance();
    }
}

但是,在多线程情况下会出现问题,会出现多个线程同时抢占的情况,因此加入synchronized锁住类就不会出现这种情况。

image-20220117223929294

public class LazyMan {
    private static LazyMan lazyMan;

    private LazyMan(){
        System.out.println(Thread.currentThread().getName()+"ok");
    }
	//双重检验
    public static LazyMan getInstance(){
         if (lazyMan == null) {		//加锁前都有可能抢占所以做2次检测
            synchronized (LazyMan.class) {
                if (lazyMan == null) {
                    lazyMan = new LazyMan();
                }
            }
            return lazyMan;
        }
    }
	//多线程并发
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                LazyMan.getInstance();
            }).start();
        }
    }
}

但是在这样处理后,我们可以发现 lazyMan = new LazyMan();是个非原子性操作,所以加上volatile字段来解决这个问题。

public class LazyMan {
    private volatile static LazyMan lazyMan;

    private LazyMan(){
        System.out.println(Thread.currentThread().getName()+"ok");
    }
	//双重检验
    public static LazyMan getInstance(){
        if (lazyMan == null) {
            synchronized (LazyMan.class) {
                if (lazyMan == null) {
                    lazyMan = new LazyMan();	//非原子性
                    /**
                         * 1、分配内存空间
                         * 2、执行构造方法,初始化对象
                         * 3、把这个对象指向这个空间
                         *
                         *  就有可能出现指令重排问题
                         *  比如执行的顺序是1 3 2 等
                         *  我们就可以添加volatile保证指令重排问题
                         */
                }
            }
            return lazyMan;
        }
    }
	//多线程并发
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                LazyMan.getInstance();
            }).start();
        }
    }
}

单例不安全, 因为反射

但是,我们仍然可以通过反射来破解,通过反射构造将私有的构造方法变成公有从而来创建新类。

public class LazyMan {
    private static LazyMan lazyMan;

    private LazyMan(){  //通过锁住,抛异常来防止反射构造类
        synchronized (LazyMan.class){
            throw new RuntimeException("不要试图使用反射破坏异常");
        }
    }

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

    public static void main(String[] args) throws InvocationTargetException, InstantiationException, IllegalAccessException {
        LazyMan instance = LazyMan.getInstance();   //通过反射创建2个类
        Constructor<LazyMan> constructor = LazyMan.class.getDeclaredAnnotation(null);
        constructor.setAccessible(true);    //公开私有制方法
        LazyMan instance2 = constructor.newInstance();
    }
}

但是,要是不创建对象,2个对象都由反射创建,则又可以破坏单例模式。

public class LazyMan {
    private static LazyMan lazyMan;

    private LazyMan(){  
        synchronized (LazyMan.class){//由于不走类创建,锁住的不是同一个。如上例,一个走这个方法,一个不走,所以能锁住。
            throw new RuntimeException("不要试图使用反射破坏异常");
        }
    }

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

    public static void main(String[] args) throws InvocationTargetException, InstantiationException, IllegalAccessException {
        //LazyMan instance = LazyMan.getInstance();   //通过反射创建2个类
        Constructor<LazyMan> constructor = LazyMan.class.getDeclaredAnnotation(null);
        constructor.setAccessible(true);    //公开私有制方法
        LazyMan instance1 = constructor.newInstance();	//2个对象都由反射创建
        LazyMan instance2 = constructor.newInstance();
    }
}

所以,我们可以通过创建一个内部的参数,来防止多个反射,,比如key = false,一个通过后将其置为true,则不能再创建。但是我们还是能够发现问题,要是通过反编译得到这个内部参数,将其重新赋值仍然可以被反射破解。

//懒汉式单例模式(最终版本)
public class LazyMan {

    private static boolean key = false;

    private LazyMan(){
        synchronized (LazyMan.class){
            if (key==false){
                key=true;
            }
            else{
                throw new RuntimeException("不要试图使用反射破坏异常");
            }
        }
        System.out.println(Thread.currentThread().getName()+" ok");
    }
    private volatile static LazyMan lazyMan;

    //双重检测锁模式 简称DCL懒汉式
    public static LazyMan getInstance(){
        //需要加锁
        if(lazyMan==null){
            synchronized (LazyMan.class){
                if(lazyMan==null){
                    lazyMan=new LazyMan();
                    /**
                     * 1、分配内存空间
                     * 2、执行构造方法,初始化对象
                     * 3、把这个对象指向这个空间
                     *
                     *  就有可能出现指令重排问题
                     *  比如执行的顺序是1 3 2 等
                     *  我们就可以添加volatile保证指令重排问题
                     */
                }
            }
        }
        return lazyMan;
    }
    //单线程下 是ok的
    //但是如果是并发的
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
        //Java中有反射
//        LazyMan instance = LazyMan.getInstance();
        Field key = LazyMan.class.getDeclaredField("key");
        key.setAccessible(true);
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true); //无视了私有的构造器
        LazyMan lazyMan1 = declaredConstructor.newInstance();
        key.set(lazyMan1,false);
        LazyMan instance = declaredConstructor.newInstance();

        System.out.println(instance);
        System.out.println(lazyMan1);
        System.out.println(instance == lazyMan1);
    }
}
3)静态内部类
//静态内部类
public class Holder {
    private Holder(){

    }
    public static Holder getInstance(){
        return InnerClass.holder;
    }
    public static class InnerClass{
        private static final Holder holder = new Holder();
    }
}
4)枚举
//enum 是什么? enum本身就是一个Class 类
public enum EnumSingle {
    INSTANCE;
    public EnumSingle getInstance(){
        return INSTANCE;
    }
}

class Test{
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        EnumSingle instance1 = EnumSingle.INSTANCE;
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
        declaredConstructor.setAccessible(true);
        //java.lang.NoSuchMethodException: com.ogj.single.EnumSingle.<init>()

        EnumSingle instance2 = declaredConstructor.newInstance();
        System.out.println(instance1);
        System.out.println(instance2);
    }
}

使用枚举,我们就可以防止反射破坏了。

image-20200812220204965

枚举类型的最终反编译源码:

public final class EnumSingle extends Enum
{

    public static EnumSingle[] values()
    {
        return (EnumSingle[])$VALUES.clone();
    }

    public static EnumSingle valueOf(String name)
    {
        return (EnumSingle)Enum.valueOf(com/ogj/single/EnumSingle, name);
    }

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

    public EnumSingle getInstance()
    {
        return INSTANCE;
    }

    public static final EnumSingle INSTANCE;
    private static final EnumSingle $VALUES[];

    static 
    {
        INSTANCE = new EnumSingle("INSTANCE", 0);
        $VALUES = (new EnumSingle[] {
            INSTANCE
        });
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值