巨详细分析单例模式!禁止套娃?

Java最简单的设计模式之一?
属于创建型模式,指一个类负责创建自己唯一的一个对象,对外提供一个的可以直接访问该对象的方式,而不需要实例化该类对象。

1.只能有一个实例
2.自己创建自己的唯一实例
3.必须对外提供该实例

安全是指普通情况下多线程安全,但除枚举方式外,均可通过反射获取多个对象

饿汉模式-多线程安全

public class HungryMan {
    private static HungryMan hungryMan=new HungryMan();
    private HungryMan(){

    }
    public static HungryMan getInstance(){
        return hungryMan;
    }
}

优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。

懒汉模式-多线程不安全

public class LazyMan {
    private static LazyMan lazyMan;
    private LazyMan(){

    }
    public static LazyMan getInstance(){
        if (lazyMan==null) {
            lazyMan = new LazyMan();
        }
        return lazyMan;
    }
}
public static void main(String[] args) {
        System.out.println(LazyMan.getInstance());
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                System.out.println(LazyMan.getInstance());
            }).start();
        }
    }

运行如下,众所周知,当多个线程同时在lazyMan对象即将赋值时失去CPU调度权,之后继续运行,就会有多个不同的对象产生

在这里插入图片描述

懒汉模式-多线程安全

public class LazyMan {
    private static LazyMan lazyMan;
    private LazyMan(){

    }
    //加锁
    public static synchronized LazyMan getInstance(){
        if (lazyMan==null) {
            lazyMan = new LazyMan();
        }
        return lazyMan;
    }
}

优点:这种实现就是在方法上加了锁,保证了多线程安全,
缺点:频繁获取该单例对象时效率有所下降。

懒汉模式-双重校验锁(DCL,即 double-checked locking)-多线程安全

public class LazyMan {
    private static LazyMan lazyMan;
    private LazyMan() {

    }
    public static LazyMan getInstance() {
    //第一个if提高效率,多个线程只有在lazyMan为null时才会等待锁,如果已经创建过单例对象则直接返回
        if (lazyMan == null) {
            synchronized (LazyMan.class) {
   //确保单例安全,lazyMan为null时,只有第一个获得锁的线程才会创建该单例对象,其余线程会直接返回
                if (lazyMan == null) {
                    lazyMan = new LazyMan();
                }
            }
        }
        return lazyMan;
    }
}

JDK1.5
是不是看起来没毛病?
然鹅, lazyMan = new LazyMan() 的操作并不是原子性的,需要经过:

  1. 为这个对象分配内存
  2. 执行构造方法,初始化对象
  3. 将对象指向内存空间

其中,步骤2和3可能会出现指令重排,
也就是执行了步骤1后,接着执行了步骤3,
假如其他线程此时到了第一个if (lazyMan == null)判断,会发现该对象的引用不为null,于是返回了该单例对象
但此时对象尚未完成初始化操作,
那么就拿到了一个不安全的单例对象

所以需要禁止指令重排,使用volatile修饰该对象即可保证线程安全

public class LazyMan {
    private volatile static LazyMan lazyMan;
    private LazyMan() {

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

静态内部类-多线程安全

public class Singleton {
    private Singleton(){
    }

    public static Singleton getInstance(){
        return InnerClass.instance;
    }

    public static class InnerClass{
        private static final Singleton instance = new Singleton();
    }
}

只有通过显式调用 getInstance 方法时,才会实例化 instance,可以实现懒加载

枚举-多线程安全且禁止反射

枚举在编译时会继承枚举类,并声明为final,天生防止反射,JDK1.5

public enum Singleton1{
    SINGLETON;
    public static Singleton1 getSingleton(){
        return SINGLETON;
    }
}
public class Test {
	public static void main(String[] args) {
        Singleton1 singleton1 = Singleton1.SINGLETON;
        //Singleton1 singleton1 = Singleton1.getSingleton();//等价于上
        Singleton1 singleton2 = Singleton1.SINGLETON;
        //Singleton1 singleton2 = Singleton1.getSingleton();//等价于上
        System.out.println("singleton1 = " + singleton1);
        System.out.println("singleton2 = " + singleton2);
        System.out.println(singleton1 == singleton2);//true
    }
}

在这里插入图片描述

ctrl+鼠标左键

在这里插入图片描述

若使用枚举的构造实例化对象则抛出异常

在这里插入图片描述

强行使用反射破环枚举单例

public class Test {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Singleton1 singleton1 = Singleton1.SINGLETON;
        Constructor<Singleton1> declaredConstructor = Singleton1.class.getDeclaredConstructor(String.class,int.class);
        declaredConstructor.setAccessible(true);
        Singleton1 singleton2 = declaredConstructor.newInstance();
        System.out.println(singleton1==singleton2);
    }
}

结果会失败并抛出异常

在这里插入图片描述

反射在双重校验锁创建下多个对象

public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        LazyMan instance = LazyMan.getInstance();//创建单例对象
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor();//获取LazyMan的无参构造器
        declaredConstructor.setAccessible(true);//设置构造器可操作
        LazyMan LazyMan1 = declaredConstructor.newInstance();//通过私有构造器实例化对象
        LazyMan LazyMan2 = declaredConstructor.newInstance();
        System.out.println("单例instance = " + instance);
        System.out.println("反射LazyMan1 = " + LazyMan1);
        System.out.println("反射LazyMan2 = " + LazyMan2);
    }

在这里插入图片描述

可以看到,三个对象完全不同,因为反射是通过操作私有构造器的方式创建出来的

双重校验锁禁止反射

public class LazyManBan {
    private volatile static LazyManBan lazyMan;

    private LazyManBan() {
        if (lazyMan != null) {
            throw new RuntimeException("请勿使用反射!");
        }
    }

    public static synchronized LazyManBan getInstance() {
        if (lazyMan == null) {
            synchronized (LazyManBan.class) {
                if (lazyMan == null) {
                    lazyMan = new LazyManBan();
                }
            }
        }
        return lazyMan;
    }
}
public class Test {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        LazyManBan instance = LazyManBan.getInstance();//创建单例对象
        Constructor<LazyManBan> declaredConstructor = LazyManBan.class.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        LazyManBan LazyManBan1 = declaredConstructor.newInstance();
        LazyManBan LazyManBan2 = declaredConstructor.newInstance();
        System.out.println("单例instance = " + instance);
        System.out.println("LazyManBan1 = " + LazyManBan1);
        System.out.println("LazyManBan2 = " + LazyManBan2);
    }
}

在这里插入图片描述

反射无法使用,你以为结束了吗?
反射无法使用是因为使用了getInstance()方法将单例对象lazyMan实例化
假如不赋值呢?也就是直接使用反射获取对象

双重校验锁不实例化对象直接反射

public class Test {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Constructor<LazyManBan> declaredConstructor = LazyManBan.class.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        LazyManBan LazyManBan1 = declaredConstructor.newInstance();
        LazyManBan LazyManBan2 = declaredConstructor.newInstance();
        LazyManBan LazyManBan3 = declaredConstructor.newInstance();
        System.out.println("LazyManBan1 = " + LazyManBan1);
        System.out.println("LazyManBan2 = " + LazyManBan2);
        System.out.println("LazyManBan3 = " + LazyManBan3);
    }
}

在这里插入图片描述

对象还是被创建出来了
原因就是因为防止反射判断的是静态的lazyMan
是一个已经定义好了的对象
然而反射每次都是通过构造函数创建的对象,并没有将静态的lazyMan实例化,也就是两者没什么关系

使用标志防止双重校验锁下多次反射

public class LazyManBanFlag {
    private static boolean flag = false;
    private volatile static LazyManBanFlag lazyMan;

    private LazyManBanFlag() {
        if (!flag) {
            flag=true;
        }else {
            throw new RuntimeException("请勿使用反射!");
        }
    }

    public static synchronized LazyManBanFlag getInstance() {
        if (lazyMan == null) {
            synchronized (LazyManBanFlag.class) {
                if (lazyMan == null) {
                    lazyMan = new LazyManBanFlag();
                }
            }
        }
        return lazyMan;
    }
}
public class Test {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        LazyManBan instance = LazyManBan.getInstance();//创建单例对象
        Constructor<LazyManBanFlag> declaredConstructor = LazyManBanFlag.class.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        LazyManBanFlag LazyManBanFlag1 = declaredConstructor.newInstance();
        LazyManBanFlag LazyManBanFlag2 = declaredConstructor.newInstance();
        System.out.println("单例instance = " + instance);
        System.out.println("LazyManBanFlag1 = " + LazyManBanFlag1);
        System.out.println("LazyManBanFlag2 = " + LazyManBanFlag2);
    }
}

在这里插入图片描述

此时无论是正常获取对象+反射
还是直接多次反射
都会抛出异常

套娃永无止境

你以为这就安全了吗?
反射也可以直接操作变量,private?
每次反射创建一个对象,设置标志为 false

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值