设计模式——单例模式

单例模式 | 菜鸟教程 这个非常的详细,本文仅仅抽取觉得重要的点进行记录。

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

单例模式分为两种:懒汉和饿汉式

饿汉式

描述:

  1. 线程安全
  2. 类加载时就初始化,浪费内存。
  3. 没有加锁,执行效率会提高。
  4. 容易产生垃圾对象。

实现

public class Singleton {  
    private static Singleton instance = new Singleton();  
    private Singleton (){}  
    public static Singleton getInstance() {  
    return instance;  
    }  
}

注意

  1. 构造方法和属性都是private的

懒汉式

描述:

  1. 第一次调用才初始化,避免内存浪费。
  2. 线程不安全
  3. 必须加锁 synchronized 才能保证单例,但加锁会影响效率。

例子

public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
  
    public static Singleton getInstance() {  
        if (instance == null) {  
            instance = new Singleton();  
        }  
        return instance;  
    }  
}

DCL双重校验/懒汉式

疑问:为什么要上两重锁

我们来看看一重锁的时候

public class DclLazySingle {
    private volatile static DclLazySingle lazySingle;
    private DclLazySingle(){

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

在new对象的时候会发生

  1. 类加载检查
  2. 分配内存
  3. 初始化零值
  4. 执行init方法

而且写入对象的期间线程是不安全的。有可能同时会排队去争夺锁,所以需要双重锁。而且为了防止指令重排,需要加入volatile。

这样做并不是完全的线程安全,单例模式可以被反射破坏

public class DclLazySingle {
    private volatile static DclLazySingle lazySingle;
    private DclLazySingle(){
    }
    public static DclLazySingle newInstances(){
        synchronized (DclLazySingle.class){
            if (lazySingle == null){
                lazySingle = new DclLazySingle();
            }
        }
        return lazySingle;
    }
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        DclLazySingle instance = DclLazySingle.newInstances();
        //反射进行破坏
        Constructor<DclLazySingle> declaredConstructor = DclLazySingle.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        DclLazySingle instance2 = declaredConstructor.newInstance();
        System.out.println(instance == instance2);
    }
}

结果

false

那怎么解决呢?

在构造方法加入限制

public class DclLazySingle {
    private volatile static DclLazySingle lazySingle;
    private DclLazySingle(){
        if (lazySingle != null){
            System.out.println("想利用反射?没门");
            throw new RuntimeException();
        }
    }
    public static DclLazySingle newInstances(){
        synchronized (DclLazySingle.class){
            if (lazySingle == null){
                lazySingle = new DclLazySingle();
            }
        }
        return lazySingle;
    }
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        DclLazySingle instance = DclLazySingle.newInstances();
        //反射进行破坏
        Constructor<DclLazySingle> declaredConstructor = DclLazySingle.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        DclLazySingle instance2 = declaredConstructor.newInstance();
        System.out.println(instance == instance2);
    }
}

结果

想利用反射?没门
Exception in thread "main" java.lang.reflect.InvocationTargetException
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
	at com.pzy.singles.DclLazySingle.main(DclLazySingle.java:27)
Caused by: java.lang.RuntimeException
	at com.pzy.singles.DclLazySingle.<init>(DclLazySingle.java:11)
	... 5 more

Process finished with exit code 1

 继续破坏

我两种方式都使用newInstance进行创建

public class DclLazySingle {
    private volatile static DclLazySingle lazySingle;
    private DclLazySingle(){
        if (lazySingle != null){
            System.out.println("想利用反射?没门");
            throw new RuntimeException();
        }
    }
    public static DclLazySingle newInstances(){
        synchronized (DclLazySingle.class){
            if (lazySingle == null){
                lazySingle = new DclLazySingle();
            }
        }
        return lazySingle;
    }
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        //反射进行破坏
        Constructor<DclLazySingle> declaredConstructor = DclLazySingle.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        DclLazySingle instance1 = declaredConstructor.newInstance();
        DclLazySingle instance2 = declaredConstructor.newInstance();
        System.out.println(instance1 == instance2);
    }
}

 结果

false

解决:

加一个临时变量,初始默认为false,当构造器运行后变为true

public class DclLazySingle {
    private static boolean flag = false;
    private volatile static DclLazySingle lazySingle;
    private DclLazySingle(){
        if (!flag){
            flag = true;
        }
        else {
            throw new RuntimeException();
        }
        if (lazySingle != null){
            System.out.println("想利用反射?没门");
            throw new RuntimeException();
        }
    }
    public static DclLazySingle newInstances(){
        synchronized (DclLazySingle.class){
            if (lazySingle == null){
                lazySingle = new DclLazySingle();
            }
        }
        return lazySingle;
    }
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        //反射进行破坏
        Constructor<DclLazySingle> declaredConstructor = DclLazySingle.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        DclLazySingle instance1 = declaredConstructor.newInstance();
        DclLazySingle instance2 = declaredConstructor.newInstance();
        System.out.println(instance1 == instance2);
    }
}

继续破坏。。。。。。利用遍历或者flag名字进行修改,所以会一直无穷无尽出现问题

枚举

public enum EnumDemo {
    INSTANCE;
    public EnumDemo getInstance(){
        return INSTANCE;
    }
}
class Test{
    public static void main(String[] args) {
        EnumDemo demo = EnumDemo.INSTANCE;
        EnumDemo demo2 = EnumDemo.INSTANCE;
        System.out.println(demo == demo2);
    }
}

为什么枚举就不会被破坏呢?

我们看看newInstance代码

public T newInstance(Object... initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        if (!this.override) {
            Class<?> caller = Reflection.getCallerClass();
            this.checkAccess(caller, this.clazz, this.clazz, this.modifiers);
        }

        if ((this.clazz.getModifiers() & 16384) != 0) {
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
        } else {
            ConstructorAccessor ca = this.constructorAccessor;
            if (ca == null) {
                ca = this.acquireConstructorAccessor();
            }

            T inst = ca.newInstance(initargs);
            return inst;
        }
    }

如果是枚举类型,就会抛出异常,因此只有枚举才能避免被反射破坏
反射在通过newInstance创建对象时,会检查该类是否ENUM修饰,如果是则抛出异常,反射失败

所以在构建唯一的实例的时候,利用枚举进行构建是一个不错的选择。

参考:

单例模式-狂神说_java 单例模式狂神说-CSDN博客

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值