spring实现单例及创建线程安全单例

一. Spring单例和多例创建
在Spring中,bean可以被定义为两种模式:prototype(多例)和singleton(单例)
singleton(单例):只有一个共享的实例存在,所有对这个bean的请求都会返回这个唯一的实例。prototype(多例):对这个bean的每次请求都会创建一个新的bean实例,类似于new。
Spring bean 默认是单例模式。

实战演示:
1,在配置文件中:

<bean id="hi" class="com.test.Hi" init-method="init" scope="singleton">

2,在测试类中代码:

 ApplicationContext context =new ApplicationContext("applicationContext.xml");
 Hi hi1 = (Hi) context.getBean("hi");
 Hi hi2 = (Hi) context.getBean("hi")
 System.out.println(hi1);
 System.out.println(hi2);

结论:二个变量指向一个对象。
3.将配置文件改为:

<bean id="hi" class="com.test.Hi" init-method="init" scope="prototype">

其他的不变,运行测试类,
结果为:每次访问bean,均创建一个新实例。

二,实现原理分析

Spring所谓单例,顾名思义,所指的就是单个实例,也就是说要保证一个类仅有一个实例。单例模式有以下的特点:
①单例类只能有一个实例
②单例类必须自己创建自己的唯一实例
③单例类必须给所有其他对象提供这一实例.

1.饿汉式单例:

Public class Singleton1
{
   Private static final Singleton1 instance=new Singleton1();      
   //私有的默认构造函数     
   Private Singleton1(){}      
   //静态工厂方法     
   Public static Singleton1 getInstance()
   {     
         return instance;     
   }    
 }   

2.懒汉式单例:

Public class Singleton2
{        
     Private static final Singleton2 instance=null;                                 
     Private Singleton2(){}        
     //静态工厂方法       
     Public synchronized static Singleton2 getInstance()
     {
          if(instance==null)
          {          
              instance=new Singleton2();        
          }        
          return instance;        
     }    
}  

//线程安全的单例创建,"双重检查锁"
public class Singleton {
    private static volatile Singleton singleton = null;

    private Singleton(){}

    public static Singleton getSingleton(){
        if(singleton == null){
            synchronized (Singleton.class){
                if(singleton == null){
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }    
}

3.恶汉式单例和懒汉式单例的比较

饿汉式单例在自己被加载时就将自己实例化,
如果从资源利用效率角度来讲,比懒汉式单例类稍差些。
但是从速度和反应时间角度来讲,则比懒汉式要稍好些。
但是遗憾的是:懒汉式单例类也不能被继承。

4.重点介绍:单例注册表
单例模式有饿汉模式、懒汉模式、静态内部类、枚举等方式实现,但由于以上模式的构造方法是私有的,不可继承,Spring为实现单例类可继承,使用的是单例注册表的方式(登记式单例)。
单例注册表单例实际上维护的是一组单例类的实例,将这些实例存储到一个Map(登记簿)中,对于已经登记过的单例,则从工厂直接返回,对于没有登记的,则先登记,而后返回
1. 使用map实现注册表;
2. 使用protect修饰构造方法;
有的时候,我们不希望在一开始的时候就把一个类写成单例模式,但是在运用的时候,我们却可以像单例一样使用他
最典型的例子就是spring,他的默认类型就是单例,spring是如何做到把不是单例的类变成单例呢?
这就用到了登记式单例
其实登记式单例并没有去改变类,他所做的就是起到一个登记的作用,如果没有登记,他就给你登记,并把生成的实例保存起来,下次你要用的时候直接给你。
IOC容器就是做的这个事,你需要就找他去拿,他就可以很方便的实现Bean的管理。

Import java.util.HashMap;    
Public class RegSingleton
{
    //使用一个map来当注册表
   Static private HashMap registry=new HashMap();    
   //静态块,在类被加载时自动执行
   Static
   {    
     RegSingleton rs=new RegSingleton();    
     registry.put(rs.getClass().getName(),rs);    
   }    
   //受保护的默认构造函数,如果为继承关系,则可以调用,克服了单 例类不能为继承的缺点    
   Protected RegSingleton(){}    
   //静态工厂方法,返回此类的唯一实例    
  public static RegSingleton getInstance(String name)
  {    
    if(name==null){    
      name=”RegSingleton”;    
    }
    if(registry.get(name)==null){    
      try
      {    
          registry.put(name,Class.forName(name).newInstance());    
       }Catch(Exception ex)
       {
           ex.printStackTrace();
       }    
    }    
    Return (RegSingleton)registry.get(name);    
   }    
} 

5.看下spring的源码:

public abstract class AbstractBeanFactory implements ConfigurableBeanFactory
{    
   /**  
    * 充当了Bean实例的缓存,实现方式和单例注册表相同  
    */    
   private final Map singletonCache=new HashMap();    
   public Object getBean(String name)throws BeansException{    
       return getBean(name,null,null);    
   }    
    ...    
   public Object getBean(String name,Class requiredType,Object[] args)throws BeansException
   {    
      //对传入的Bean name稍做处理,防止传入的Bean name名有非法字符(或则做转码)    
      String beanName=transformedBeanName(name);    
      Object bean=null;    
      //手工检测单例注册表    
      Object sharedInstance=null;    
      //使用了代码锁定同步块,原理和同步方法相似,但是这种写法效率更高    
      synchronized(this.singletonCache){    
         sharedInstance=this.singletonCache.get(beanName);    
       }    
      if(sharedInstance!=null){    
         ...    
         //返回合适的缓存Bean实例    
         bean=getObjectForSharedInstance(name,sharedInstance);    
      }else{    
        ...    
        //取得Bean的定义    
        RootBeanDefinition mergedBeanDefinition=getMergedBeanDefinition(beanName,false);    
         ...    
        //根据Bean定义判断,此判断依据通常来自于组件配置文件的单例属性开关    
        //    
        //如果是单例,做如下处理    
        if(mergedBeanDefinition.isSingleton()){    
           synchronized(this.singletonCache){    
            //再次检测单例注册表    
             sharedInstance=this.singletonCache.get(beanName);    
             if(sharedInstance==null){    
                ...    
               try {    
                  //真正创建Bean实例    
                  sharedInstance=createBean(beanName,mergedBeanDefinition,args);    
                  //向单例注册表注册Bean实例    
                   addSingleton(beanName,sharedInstance);    
               }catch (Exception ex) {    
                  ...    
               }finally{    
                  ...    
              }    
             }    
           }    
          bean=getObjectForSharedInstance(name,sharedInstance);    
        }    
       //如果是非单例,即prototpye,每次都要新创建一个Bean实例    
       //    
       else{    
          bean=createBean(beanName,mergedBeanDefinition,args);    
       }    
}    
...    
   return bean;    
}    
}

==================================================================
下面补充下其他的创建线程安全的单例方式:

1.通过静态内部类来实现:
public class Singleton {
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
    private Singleton (){}
    public static final Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}
这种方式相比前面两种有所优化,就是使用了lazy-loading。
Singleton类被装载了,但是instance并没有立即初始化。
因为SingletonHolder类没有被主动使用,只有显示通过调用getInstance方法时,
才会显示装载SingletonHolder类,从而实例化instance

2.使用CAS。
CAS是项乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,
只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,
而是被告知这次竞争中失败,并可以再次尝试。
public class Singleton 
{
    private static final AtomicReference<Singleton> INSTANCE = new AtomicReference<Singleton>();

    private Singleton() {}

    public static Singleton getInstance() 
    {
        for (;;) 
        {
            Singleton singleton = INSTANCE.get();
            if (null != singleton) 
            {
                return singleton;
            }
            singleton = new Singleton();
            if (INSTANCE.compareAndSet(null, singleton))
             {
                return singleton;
            }
        }
    }
}

用CAS的好处在于不需要使用传统的锁机制来保证线程安全,CAS是一种基于忙等待的算法,依赖底层硬件的实现,相对于锁它没有线程切换和阻塞的额外消耗,可以支持较大的并行度。
CAS的一个重要缺点在于如果忙等待一直执行不成功(一直在死循环中),会对CPU造成较大的执行开销。
另外,如果N个线程同时执行到singleton = new Singleton();的时候,
会有大量对象创建,很可能导致内存溢出。

你可能感兴趣的:(java)