前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >设计模式之单例模式

设计模式之单例模式

作者头像
路行的亚洲
发布2020-12-02 09:37:56
2620
发布2020-12-02 09:37:56
举报
文章被收录于专栏:后端技术学习后端技术学习

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

代码语言:javascript
复制
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

代码语言:javascript
复制
Object sharedInstance = getSingleton(beanName);

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

代码语言:javascript
复制
/**
 * 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进行修饰。

常见的单例模式中:

饿汉式:

代码语言:javascript
复制
/**
 * 获取单例对象
 */
public class Singleton {
    //创建对象
    private static Singleton instance = new Singleton();

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

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

}

懒汉式:

代码语言:javascript
复制
/**
 * 获取单例对象
 */
public class Singgleton {
    //创建对象
    private static Singgleton singleton = null;
    //提供空参构造函数
    private Singgleton(){};

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

}

使用双重校验:

代码语言:javascript
复制
/**
 * 创建单例对象 双重校验
 */
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来解决这个问题,因为内存屏障是在读和写中加入屏障,从而避免其指令重排,从而解决指令重排的问题。

代码语言:javascript
复制
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作者推荐的单例模式,枚举:

代码语言:javascript
复制
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;
    }
 }
}

单例模式的使用场景:

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

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

代码语言:javascript
复制
/**
 * 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

代码语言:javascript
复制
/**
 * 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)。

代码语言:javascript
复制
/**
 * 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;
    }
}
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-11-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 后端技术学习 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档