单例设计模式 Singleton 入门到源码,源码变得优美了!

    科技2022-07-10  111

    饿汉式(单例模式)

    饿汉就是说创建类的时候,把对象也创建出来了

    优点:这种写法比较简单,就是在类装载的时候就完成了实例化。避免了线程同步的问题

    缺点:在类装载的时候就完成实力化,没有达到Lazy Loading 的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费

    结论:这种单例模式可用,可能造成内存浪费

    第一种代码(饿汉式-静态变量):

    package com.atshiyou.singleton; public class SingleTest01 { public static void main(String[] args) { //测试: Singleton instance = Singleton.getInstance(); Singleton instance2 = Singleton.getInstance(); System.out.println(instance==instance2); System.out.println("instance.hashcode="+instance.hashCode()); System.out.println("instance2.hashcode="+instance2.hashCode()); } } //饿汉式(静态变量) class Singleton{ //1. 构造器私有化,外部不能new private Singleton() { } //2.本类内部创建对象实例 private final static Singleton instance = new Singleton(); //提供一个公有的静态方法,返回实例对象 public static Singleton getInstance() { return instance; } }

    第二种代码(饿汉式-静态代码块):

    package com.atshiyou.singleton.type2; public class SingleTest02 { public static void main(String[] args) { //测试: Singleton instance = Singleton.getInstance(); Singleton instance2 = Singleton.getInstance(); System.out.println(instance==instance2); System.out.println("instance.hashcode="+instance.hashCode()); System.out.println("instance2.hashcode="+instance2.hashCode()); } } //饿汉式(静态变量) class Singleton{ //1. 构造器私有化,外部能new private Singleton() { } //2.本类内部创建对象实例 private final static Singleton instance; static { // 在静态代码块中,创建单例对象 instance = new Singleton(); } //提供一个公有的静态方法,返回实例对象 public static Singleton getInstance() { return instance; } }

    优缺点说明: 1)]这种方式和上面的方式其实类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。优缺点和上面是一样的。 2)结论:这种单例模式可用,但是可能造成内存浪费

     

    懒汉式(单例模式)

    一 、线程不安全写法:

    优缺点说明:

    1)起到了懒加载的效果,但是只能在单线程下使用

    2)、如果在多线程下,一个线程进入了if(singleton == null )判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式

    3)、结论:在实际开发中,不要使用这种方式

     

     

    二、懒汉式线程安全写法

     

    提供了一个静态的公有方法,加了同步代码 synchronized,解决线程安全的问题

    优缺点:

    1)、效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。方法进行同步效率太低

    2)、解决了线程同步的问题

    3)、实际开发中,不推荐此方法

     

    三、双重检查写法 if

    1)、double-check 概念是多线程开发中常使用到的,如代码中所示,我们进行了两次if(singleton == null)检查,这样就可以保证线程安全了

    2)、这样,实例化代码只执行了一次,后面再次访问时,判断if(singleton == null),直接return实例化对象,也避免重复进行方法同步

    3)、线程安全,延迟加载,效率极高

    4)、推荐使用

     

    代码:增加一个 volatile ,作用:可见性,防止指令重排

    两个 if 语句

     

    静态内部类

    1)这种方式采用了类装载的机制来保证初始化实例时只有一个线程。 2)静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化 时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的 实例化。 3)类的静态属性只会在第一次加载类的时候初始化,所以在这里,VM帮助我们 保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。 4))优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高 5)结论:推荐使用.

     

     

    枚举(单例)

    推荐使用

    优缺点说明: 这借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的 对象。 这种方式是Effective Java作者Josh Bloch提倡的方式 结论:推荐使用  

     

    以上的几种创建单例模式,只有枚举、静态内部类、双重检查 + 饿汉式(静态变量)推荐使用,其他均不推荐

     

     

    单例模式注意事项和细节说明:

    1)、单例模式保证了 系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统的性能

    2)、当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new

    单例模式在Singleton里的源码

     

    在 DefaultSingletonBeanRegistery文件下记录了 获取单例bean的代码:

    老版本的getSingleton

    /** * Return the (raw) singleton object registered under the given name. * <p>Checks already instantiated singletons and also allows for an early * reference to a currently created singleton (resolving a circular reference). * @param beanName the name of the bean to look for * @param allowEarlyReference whether early references should be created or not * @return the registered singleton object, or {@code null} if none found */ protected Object getSingleton(String beanName, boolean allowEarlyReference) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return (singletonObject != NULL_OBJECT ? singletonObject : null); }

    新版本的2020 10/12 查看的 代码是:看起来更加优美了

    allowEarlyReference的中文解释 whether early references should be created or not 是否应创建早期引用,老师的解释是allowEarlyReference 是否允许从singletonFactories中通过getObject拿到对象

    @Nullable protected Object getSingleton(String beanName, boolean allowEarlyReference) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { synchronized(this.singletonObjects) { singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null) { ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } } } return singletonObject; }

    第一个map是singletonObjects------------ 单例池   spring容器

    第二个map是 singletonFactories 工厂

    第三个是earlySingletonObjects 三级缓存

    (ps:---spring在创建对象的时候是分为两步:1、通过反射创建出一个对象,2、往对象中的属性赋值   

        我们很好理解第一步,我们得到对象的全路径名,就可以利用反射创建A对象,得到A对象中的属性。这时我们会把这个A对象标为正在创建中,并且放到缓存中

    跟面试官可以扯扯普通类的实例化过程:一个类对象(class User) 是一个java文件,经过javac的编译,变成一个.class文件,字节码,里面包含了成员变量,有构造方法和成员方法,然后通过类加载器,JVM虚拟机将磁盘里的文件加载到内存中(jvm虚拟机是通过main方法启动的),当遇到new这个关键字时,在方法区里提供的模板,在堆上分配空间存储这个对象。具体可以参考JVM虚拟机中 栈 和 堆的关系 + 堆 和 方法区关系,spring bean的具体实例化进程可以参考 spring bean 的实例化过程

    ---)

    从上面代码可以看到,spring依赖注入时,使用了 双重判断加锁 的单例模式。

    首先从缓存singletonObjects(实际上是一个map)中获取bean实例,如果为null,对缓存singletonObjects加锁,然后再从缓存中获取bean,如果继续为null,就创建一个bean。

     

    在这里Spring并没有使用私有构造方法来创建bean,而是通过 singletonFactory.getObject() 返回具体beanName对应的ObjectFactory来创建bean。

     

    必看:单例模式的总结可以查看 博客:spring如何解决循环依赖的 节约时间请直接跳到该博客的红字部分解决方法

     

     

     

    Processed: 0.030, SQL: 8