专栏首页后端技术学习设计模式之单例模式

设计模式之单例模式

在前面中,我们知道如果一个bean需要被加载,首先需要获取资源的位置,然后根据资源位置获取xml文件,然后将其变成document,然后根据document对元素进行解析,然后放入beanDefintionMap中,然后通过getBean获取bean,而这个过程是bean加载的过程。而这里关注的重点是doGetBean。

public class BeanFactoryTest {

    public static void main(String[] args) {
        //获取xml资源
        Resource resource = new ClassPathResource("spring-factory.xml");
        //获取bean工厂
        BeanFactory beanFactory = new XmlBeanFactory(resource);

        //获取bean
        Product car1 = (Product) beanFactory.getBean("car");
        Product car2 = (Product) beanFactory.getBean("car");
        car1.show();

        System.out.println(car1 == car2);
    }
}

在getBean中,在这个过程中,首先会将bean进行转换,然后执行获取单例对象操作,然后执行后续操作。而获取单例的过程是值得我们学习的。

AbstractBeanFactory#doGetBean

Object sharedInstance = getSingleton(beanName);

获取单例对象:采用的是单例模式,这里采用双重校验double check。

/**
 * 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
 */
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
  //获取单例bean对象  
   Object singletonObject = this.singletonObjects.get(beanName);
    //如果单例bean对象为空,同时当前被创建对象,则首先对单例对象进行锁定,进行再一次判空,然后进行获取
   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;
}

单例模式的创建过程:首先定义类,在类的基础上定义对象,提供空参构造函数,然后基于对象创建一个方法获取单例对象,而为了防止获取单例对象出现并发问题,需要对获取的单例对象进行double check,同时定义的对象为了防止出现指令重排的问题,需要加内存屏障,因此可以加volitale进行修饰。

常见的单例模式中:

饿汉式:

/**
 * 获取单例对象
 */
public class Singleton {
    //创建对象
    private static Singleton instance = new Singleton();

    //提供空参构造
    private Singleton() {
    }

    //返回对象
    public static Singleton getInstance() {
        return instance;
    }

}

懒汉式:

/**
 * 获取单例对象
 */
public class Singgleton {
    //创建对象
    private static Singgleton singleton = null;
    //提供空参构造函数
    private Singgleton(){};

     //静态工厂方法
    public static Singgleton getSingleton(){
        if(singleton==null){
            singleton = new Singgleton();
        }
        return singleton;
    }

}

使用双重校验:

/**
 * 创建单例对象 双重校验
 */
public class Singleton1 {
    //定义对象
    private static Singleton1 singleton = null;
    //提供空参构造
    private Singleton1(){}

    //使用双重校验锁 double check,返回创建对象
    public static Singleton1 getSingleton(){
        if(singleton==null){
            synchronized (Singleton1.class){
                if(singleton==null) {
                    singleton = new Singleton1();
                }
            }
        }
        return singleton;
    }
}

但是采用双重校验还是会存在指令重排的问题,而解决指令重排的问题,则可以采用内存屏障解决这个问题,此时可以借助volitale来解决这个问题,因为内存屏障是在读和写中加入屏障,从而避免其指令重排,从而解决指令重排的问题。

public class Single {
    private  static volatile Single singleStance = null;
    private Single(){
        System.out.println("Single 的构造方法被执行了!!!");
    }
    //DCL: double check lock 双端检索机制
    public static Single getSingleStance() {
        if (singleStance == null) {
            synchronized (Single.class) {
                if (singleStance == null) {
                    singleStance = new Single();
                }
            }
        }
        return singleStance;
    }
}

除了上面的,还有一个effective java作者推荐的单例模式,枚举:

public class Singleton{
  // 私有构造函数
  private Singleton() {} 

  public static Singleton getInstance() {
    return Singleton.INSTANCE.getInstance();
  }

  private enum Singleton {
    INSTANCE;

    private Singleton singleton;

    // JVM保证这个方法绝对只调用一次
    Singleton() {
        singleton = new Singleton();
    }

    public Singleton getInstance() {
        return singleton;
    }
 }
}

单例模式的使用场景:

1.Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。 
2.应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。 
3.数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源
4.多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制
5.spring的bean默认也是单例模式,springMVC是单例模式
6.mysql,redis等的连接对象使用单例模式

当然除了我们看到的上面的获取单例的方法,其实在spring的源码中,我们还可以看到一个增强版的获取单例的方法,这个getSingleton方法除了dubbo check之外,还在其前后做了后置处理的增强:

/**
 * Return the (raw) singleton object registered under the given name,
 * creating and registering a new one if none registered yet.
 * @param beanName the name of the bean
 * @param singletonFactory the ObjectFactory to lazily create the singleton
 * with, if necessary
 * @return the registered singleton object
 */
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
   Assert.notNull(beanName, "Bean name must not be null");
   synchronized (this.singletonObjects) {
      Object singletonObject = this.singletonObjects.get(beanName);
      if (singletonObject == null) {
         if (this.singletonsCurrentlyInDestruction) {
            throw new BeanCreationNotAllowedException(beanName,
                  "Singleton bean creation not allowed while singletons of this factory are in destruction " +
                  "(Do not request a bean from a BeanFactory in a destroy method implementation!)");
         }
         if (logger.isDebugEnabled()) {
            logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
         }
         //单例对象创建前的操作 
         beforeSingletonCreation(beanName);
         boolean newSingleton = false;
         boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
         if (recordSuppressedExceptions) {
            this.suppressedExceptions = new LinkedHashSet<>();
         }
         try {
            singletonObject = singletonFactory.getObject();
            newSingleton = true;
         }
         catch (IllegalStateException ex) {
            // Has the singleton object implicitly appeared in the meantime ->
            // if yes, proceed with it since the exception indicates that state.
            singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
               throw ex;
            }
         }
         catch (BeanCreationException ex) {
            if (recordSuppressedExceptions) {
               for (Exception suppressedException : this.suppressedExceptions) {
                  ex.addRelatedCause(suppressedException);
               }
            }
            throw ex;
         }
         finally {
            if (recordSuppressedExceptions) {
               this.suppressedExceptions = null;
            }
            //单例对象创建后的处理 
            afterSingletonCreation(beanName);
         }
         if (newSingleton) {
            addSingleton(beanName, singletonObject);
         }
      }
      return singletonObject;
   }
}

单例模式的使用场景可以在dubbo中可以看到应用:

ServiceClassPostProcessor#resolveBeanNameGenerator

/**
 * It'd better to use BeanNameGenerator instance that should reference
 * {@link ConfigurationClassPostProcessor#componentScanBeanNameGenerator},
 * thus it maybe a potential problem on bean name generation.
 *
 * @param registry {@link BeanDefinitionRegistry}
 * @return {@link BeanNameGenerator} instance
 * @see SingletonBeanRegistry
 * @see AnnotationConfigUtils#CONFIGURATION_BEAN_NAME_GENERATOR
 * @see ConfigurationClassPostProcessor#processConfigBeanDefinitions
 * @since 2.5.8
 */
private BeanNameGenerator resolveBeanNameGenerator(BeanDefinitionRegistry registry) {

    BeanNameGenerator beanNameGenerator = null;

    if (registry instanceof SingletonBeanRegistry) {
        SingletonBeanRegistry singletonBeanRegistry = SingletonBeanRegistry.class.cast(registry);
        beanNameGenerator = (BeanNameGenerator) singletonBeanRegistry.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR);
    }

    if (beanNameGenerator == null) {

        if (logger.isInfoEnabled()) {

            logger.info("BeanNameGenerator bean can't be found in BeanFactory with name ["
                    + CONFIGURATION_BEAN_NAME_GENERATOR + "]");
            logger.info("BeanNameGenerator will be a instance of " +
                    AnnotationBeanNameGenerator.class.getName() +
                    " , it maybe a potential problem on bean name generation.");
        }

        beanNameGenerator = new AnnotationBeanNameGenerator();

    }

    return beanNameGenerator;

}

对于设计模式中的应用,前面我们看到的观察者模式同样也在dubbo中得到了使用。在之前的dubbo版本中,使用的是自定义标签的方式进行的bean注入。

在dubbo的2.7版本中,我们可以看到dubbo的版本是基于spring的事件进行bean的初始化操作的,采用的观察者模式实现的:applicationEventPublisher.publishEvent(exportEvent)。

/**
 * ServiceFactoryBean
 *
 * @export
 */
public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean, DisposableBean,
        ApplicationContextAware, BeanNameAware, ApplicationEventPublisherAware {


    private static final long serialVersionUID = 213195494150089726L;

    private final transient Service service;

    private transient ApplicationContext applicationContext;

    private transient String beanName;

    private ApplicationEventPublisher applicationEventPublisher;

    public ServiceBean() {
        super();
        this.service = null;
    }

    public ServiceBean(Service service) {
        super(service);
        this.service = service;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
        SpringExtensionFactory.addApplicationContext(applicationContext);
    }

    @Override
    public void setBeanName(String name) {
        this.beanName = name;
    }

    /**
     * Gets associated {@link Service}
     *
     * @return associated {@link Service}
     */
    public Service getService() {
        return service;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        if (StringUtils.isEmpty(getPath())) {
            if (StringUtils.isNotEmpty(getInterface())) {
                setPath(getInterface());
            }
        }
    }

    /**
     * Get the name of {@link ServiceBean}
     *
     * @return {@link ServiceBean}'s name
     * @since 2.6.5
     */
    @Parameter(excluded = true)
    public String getBeanName() {
        return this.beanName;
    }

    /**
     * @since 2.6.5
     */
    @Override
    public void exported() {
        super.exported();
        // Publish ServiceBeanExportedEvent
        publishExportEvent();
    }

    /**
     * @since 2.6.5
     */
    private void publishExportEvent() {
        ServiceBeanExportedEvent exportEvent = new ServiceBeanExportedEvent(this);
        applicationEventPublisher.publishEvent(exportEvent);
    }

    @Override
    public void destroy() throws Exception {
        // no need to call unexport() here, see
        // org.apache.dubbo.config.spring.extension.SpringExtensionFactory.ShutdownHookListener
    }

    // merged from dubbox
    @Override
    protected Class getServiceClass(T ref) {
        if (AopUtils.isAopProxy(ref)) {
            return AopUtils.getTargetClass(ref);
        }
        return super.getServiceClass(ref);
    }

    /**
     * @param applicationEventPublisher
     * @since 2.6.5
     */
    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }
}

本文分享自微信公众号 - 后端技术学习(gh_9f5627e6cc61),作者:路行的亚洲

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-11-28

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • mybatis学习二

    前面我们通过看到mybatis中的session中的sqlSessionTest中,为我们提供了面向sqlSession的编程方式。但是对于面向对象的方式,这种...

    路行的亚洲
  • LinkedBlockingQueue源码学习

    采用线程池和阻塞队列实现生产/消费者模型。其中LinkedBlockingQueue是阻塞队列,同时线程安全,其特点:

    路行的亚洲
  • 设计模式之设计原则

    单一职责原则:类的职责单一,不能将太多的职责放在一个类中,该原则是实现高内聚、低耦合的指导方针

    路行的亚洲
  • 体检套餐管理系统 -- Dictionary<K,V>双列集合

    本文章为 Dictionary<K,V>双列集合开发项目,如需要List<T>单列集合开发的此项目,请到楼主博客园寻找 博客网址:http://www.cnbl...

    房上的猫
  • lombok使用基础教程

    前言 lombok是一个编译级别的插件,它可以在项目编译的时候生成一些代码。在很多工具类的项目中都有这个功能。比如dagger。 通俗的说,lombok可以通过...

    Ryan-Miao
  • 探索JAVA并发 - 可重入锁和不可重入锁

    CAS操作需要输入两个数值,一个旧值(期望操作前的值)和一个新值,在操作期间先比较下旧值有没有发生变化,如果没有发生变化,才交换成新值,发生了变化则不交换。

    李红
  • Autofac在.NET Core 中的使用

    Autofac 是一款.NET IoC 容器 . 它管理类之间的依赖关系, 从而使应用在规模及复杂性增长的情况下依然可以轻易地修改 。.NET CORE 中也内...

    心莱科技雪雁
  • 【C#】分享一个可携带附加消息的增强消息框MessageBoxEx

    --------------201806111122更新---------------

    AhDung
  • .NET 云原生架构师训练营(模块二 基础巩固 EF Core 查询)--学习笔记

    加载相关数据:https://docs.microsoft.com/zh-cn/ef/core/querying/related-data/

    郑子铭
  • .NET 云原生架构师训练营(模块二 基础巩固 EF Core 查询)--学习笔记

    加载相关数据:https://docs.microsoft.com/zh-cn/ef/core/querying/related-data/

    郑子铭

扫码关注云+社区

领取腾讯云代金券