学记-设计模式-单例模式

本文详细介绍了Java中的单例模式,包括饿汉式、懒汉式、懒汉式-方法级锁、双重锁校验、枚举、CAS以及静态内部类等多种实现方式,并探讨了它们的线程安全性和效率。同时,文章还通过反射展示了单例模式在某些情况下的可破坏性,并提供了防御策略。
摘要由CSDN通过智能技术生成

学记-设计模式-单例模式

前言

​ 最近在准备面试,梳理知识的时候发现自己总是边学边忘,很多知识点容易混淆。经过反思发现,我这个死脑筋就是记不住好。所以我决定把他们都记在笔记里,这样既可以加深我印象,又可以和大家一起学习讨论。

​ 说回单例模式,最近看了小傅哥遇见狂神说的设计模式,有很多有趣的地方。本文是对单例模式的学习笔记。

介绍

​ 单例模式,属于创建型模式,也就是要保证一个类仅有一个实例,并提供一个访问它的全局访问点。

实现

​ 单例模式的实现方式比较多,我这边列举7种方式。

1、饿汉式(线程安全)
public class Hungry_0 {

    private static Hungry_0 instance = new Hungry_0();

    public Hungry_0() {
    }

    public static Hungry_0 getInstance() {
        return instance;
    }

}
  • 在程序启动的时候直接运行加载,后续有外部需要使用的时候获取即可。

  • 但是这种方式有个不好的地方就是会提前加载资源到内存中,就好比,你打开的是应用的个人中心,应用却把非个人中心的资源也加载了。

2、懒汉式(线程不安全)
private static Lazy_0 INSTANCE;
    private Lazy_0() {}
    private static Lazy_0 getInstance() {
        if (null == INSTANCE) {
            INSTANCE = new Lazy_0();
        }
        return INSTANCE;
    }
  • 在多线程情况下不安全,并发问题。
3、懒汉式-方法级锁(线程安全)
private static Lazy_1 INSTANCE;

    private Lazy_1() {
    }
    private synchronized static Lazy_1 getInstance() {
        if (null == INSTANCE) {
            INSTANCE = new Lazy_1();
        }
        return INSTANCE;
    }
  • 虽然是安全的,但由于把锁加到方法上后,所有的访问都因需要锁占用导致资源的浪费。如果不是特殊情况下,不建议此种方式实现单例模式。
4、懒汉式-双重锁校验(线程安全)
	//volatile 防止指令重排    
	private volatile static Lazy_2 INSTANCE;

    private Lazy_2() {
    }
    private static Lazy_2 getInstance() {
        if (null == INSTANCE) {
            synchronized (Lazy_2.class){
                if (null == INSTANCE) {
                    INSTANCE = new Lazy_2();
                }
            }
        }
        return INSTANCE;
    }
  • 双重锁的方式是方法级锁的优化,减少了部分获取实例的耗时。

  • 但是在特定情况下不安全,不过比较少见,可以用来炫技,比如反射。

   //尝试使用反射创建一个新的实例
   Lazy_2 instance = Lazy_2.getInstance();
   Constructor<Lazy_2> constructor = 	 	  (Constructor<Lazy_2>)instance.getClass().getDeclaredConstructor(null);
   constructor.setAccessible(true);
   Lazy_2 clone = constructor.newInstance();
   System.out.println(instance); //lazy.Lazy_2@39a054a5
   System.out.println(clone); //lazy.Lazy_2@71bc1ae4

怎样防止上面的反射创建呢,我们可以在构造方法中加入反射校验

    //volatile 防止指令重排
    private volatile static Lazy_3 INSTANCE;

    private Lazy_3() {
        if (null != INSTANCE) {
            throw new RuntimeException("Cannot reflectively create use invoke");
        }
    }
    private static Lazy_3 getInstance() {
        if (null == INSTANCE) {
            synchronized (Lazy_3.class) {
                if (null == INSTANCE) {
                    INSTANCE = new Lazy_3();
                }
            }
        }
        return INSTANCE;
    }

当然,还有别的方法可以再次创建实例。(道高一尺,魔高一丈)

5、懒汉式-枚举(线程安全)
public enum Lazy_4 {
    
    INSTANCE;

    private static Lazy_4 getInstance() {
        return INSTANCE;
    }
}
  • 为什么枚举类是线程安全的呢,通过源码查看发现枚举类也是私有构造器。

    尝试使用反射去new一个新的实例

            Lazy_4 instance = Lazy_4.getInstance();
            System.out.println(instance.hashCode());//lazy.Lazy_4@39a054a5
            //尝试使用多次方式创建对象
            Constructor<Lazy_4> constructor = Lazy_4.class.getDeclaredConstructor(null);
    		constructor.setAccessible(true);
            System.out.println(constructor.newInstance());
    

    却提示找不到该构造方法,找不到构造方法??

    为什么找不到呢,经过一系列查找,最终通过字节码查看下发现,并没有无参构造方法。

再试试反射,之后出现了“Cannot reflectively create enum objects”,原来是在newInstance的时候会判断该类是否是枚举类,防止其多次创建实例。

6、懒汉式- CAS(线程安全)
private static final AtomicReference<Lazy_5> INSTANCE = new AtomicReference<Lazy_5>();

    private static boolean flag = true;
    private Lazy_5() {
    }
    private static final Lazy_5 getInstance() {
        for (; ; ) {
            Lazy_5 instance = INSTANCE.get();
            if (null != instance) return instance;
            INSTANCE.compareAndSet(null, new Lazy_5());
            return INSTANCE.get();
        }
    }
  • 比较替换的方法,当然CAS也有一个缺点就是忙等,如果一直没有获取到将会处于死循环中。
7、懒汉式- 静态内部类(线程安全)
    private static class SingletonHolder {
        private static Lazy_6 instance = new Lazy_6();
    }
    private Lazy_6() {
    }
    public static Lazy_6 getInstance() {
        return SingletonHolder.instance;
    }
总结

这也是我第一次把学习笔记记在这里,如果有哪里写得不对的地方,也请大家指出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值