单例设计模式

单例设计模式

概念:单例对象的类必须保证只有⼀个实例存在。

适⽤场景: 单例模式只允许创建⼀个对象,因此节省内存,加快对象访问速度,因此对象需要被公⽤的场合适合使⽤,如多个模块使⽤同⼀个数据源连接对象等等。如:

  1. 需要频繁实例化然后销毁的对象。
  2. 创建对象时耗时过多或者耗资源过多,但⼜经常⽤到的对象。
  3. 有状态的⼯具类对象。
  4. 频繁访问数据库或⽂件的对象。

常⻅写法:

public class Singleton {
    /**
     * 优点:没有线程安全问题,简单
     * 缺点:提前初始化会延⻓类加载器加载类的时间;如果不使⽤会浪费内存空间;不能传递参数
     */
    private static final Singleton instance = new Singleton();
    private Singleton(){};
    public static Singleton getInstance(){
        return instance;
        }
}

饿汉式:在实例化类的时候就会创建对象。

优点:

  • 会提前初始化,你调用的时候就不需要再去初始化。性能是好的。
  • 没有线程安全问题,简单

缺点:

  • 提前初始化会延⻓类加载器加载类的时间;
  • 如果不使⽤会浪费内存空间,因为,即使不用它,它也会在那;
  • 不能传递参数。
public class Singleton{
    /**
    * 优点:解决线程安全,延迟初始化( Effective Java推荐写法)
    */
    private Singleton(){}
    public static Singleton getInstance () {
        return Holder.SINGLE_TON;
        }
    private static class Holder{
        private static final Singleton SINGLE_TON = new Singleton();
    }
}

懒汉式:延迟初始化的手段

调用它的时候,会去初始化;不调用它的时候,就不初始化。

优点:

解决线程安全,延迟初始化( Effective Java推荐写法)

 public class Singleton {
     private volatile static Singleton uniqueSingleton;
     private Singleton() {
     }
     
     public Singleton getInstance() {
         if (null == uniqueSingleton) {//第一重检查
             synchronized (Singleton.class) {//第一重加锁
                 if (null == uniqueSingleton) {//第二重检查
                     uniqueSingleton = new Singleton();//创建对象
                 }
             }
         }
         return uniqueSingleton;
     }
 }        

双重检查锁原理:

第一次判断为空以后,第一次加锁。

因为加锁不是一个原子操作,在加锁的过程中,uniqueSingleton对象可能已经被初始化掉了,所以加锁完毕以后,还需要去判断一下,uniqueSingleton对象是不是为空?

加锁后,进行第二次检验(检查)

如果uniqueSingleton对象为空,进行创建对象。

注意:

volatile

  • 会保证可见性
  • 不会保证原子性。

具体原理:

加了一个log的前置指令(即内存屏障),会保证屏障后面的指令不会放到屏障之前,也不会把屏障之前的指令放到屏障之后。在执行到屏障的时候,一定会保证屏障之前的指令全执行完了。

进行写操作的时候:

  • 有缓存:写操作会导致缓存失效;
  • 无缓存:将缓存里的修改,直接写入到缓存里。

volatile的作用:

  1. 阻止指令重排序;

    加入volatile,会加入一个内存屏障。加入内存屏障后顺序就不能变了。是禁止指令重排序。

  1. 保证可见性;

    平时操作时,会有一个主内存和一个工作内存的。

    如果不加volatile,数据是放在工作内存的,进行操作的时候,需要从工作内存刷到主内存。加了volatile,相当于不用工作内存,直接用主内存。相当于多个线程对一个对象操作的时候,多个线程对结果是可见的。

为什么考虑指令重排序?

volatile指令重排序

在执⾏程序时,为了提供性能,处理器和编译器常常会对指令进⾏重排序,但是不能随意重排序,不是你 想怎么排序就怎么排序,它需要满⾜以下两个条件:

  1. 在单线程环境下不能改变程序运⾏的结果;
  2. 存在数据依赖关系的不允许重排序

指令排序

uniqueSingleton = new Singleton();
  1. 分配内存空间
  2. 初始化对象
  3. 将对象指向刚分配的内存空间

但是有些编译器为了性能的原因,可能会将第⼆步和第三步进⾏重排序,顺序就成了:

  1. 分配内存空间
  2. 将对象指向刚分配的内存空间
  3. 初始化对象

现在考虑重排序后,两个线程发⽣了以下调⽤:

Time Thread A Thread B
T1 检查到uniqueSingleton为空
T2 获取锁
T3 再次检查到uniqueSingleton为 空
T4 为uniqueSingleton分配内存空 间
T5 将uniqueSingleton指向内存空 间
T6 检查到uniqueSingleton不为空
T7 访问uniqueSingleton(此时对 象还未完成初始化)
T8 初始化uniqueSingleton

在这种情况下,T7时刻线程B对uniqueSingleton的访问,访问的是⼀个初始化未完成的对象。

使⽤了volatile关键字后,重排序被禁⽌,所有的写(write)操作都将发⽣在读(read)操作之前。

单例模式的破坏

Singleton sc1 = Singleton.getInstance(); 
Singleton sc2 = Singleton.getInstance(); 
System.out.println(sc1); // sc1,sc2是同⼀个对象 
System.out.println(sc2); 

/*通过反射的⽅式直接调⽤私有构造器*/ 
Class clazz = (Class) Class.forName("com.le arn.example.Singleton"); 
Constructor c = clazz.getDeclaredConstructor(null); 
c.setAccessible(true); // 跳过权限检查 
Singleton sc3 = c.newInstance(); 
Singleton sc4 = c.newInstance(); 
System.out.println("通过反射的⽅式获取的对象sc3:" + sc3); // sc3,sc 4不是同⼀个对象  
System.out.println("通过反射的⽅式获取的对象sc4:" + sc4); 
//以上代码是说明单例是可以被破坏的

//防⽌反射获取多个对象的漏洞 (即防止单例被破坏的解决办法。)
private Singleton() { 
    if (null != SingletonClassInstance.instance) 
        throw new RuntimeException(); 
}

spring中bean的单例

a. 当多个⽤户同时请求⼀个服务时,容器会给每⼀个请求分配⼀个线程,这时多个线程会并发执⾏该请求对应的业务逻辑(成员⽅法)。此时就要注意了,如果该处理逻辑中有对单例状态的修改(体现为该单例的成员属性),则必须考虑线程同步问题。

有状态就是有数据存储功能有状态对象(Stateful Bean)就是有实例变量的对象,可以保存数据,是⾮线程安全的。在不同⽅法调⽤间不保留任何状态。

⽆状态就是⼀次操作,不能保存数据。⽆状态对象(Stateless Bean),就是没有实例变量的对象 . 不能保存数据,是不变类,是线程安全的。

b. 实现

public abstract class AbstractBeanFactory implements Configurable
    BeanFactory{
    /**
    * 充当了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定义判断,此判断依据通常来⾃于组件配置⽂件的单例属性开关
                  //<bean id="date" class="java.util.Date" scope="singleton"/>
                  //如果是单例,做如下处理
                  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实例
              //<bean id="date" class="java.util.Date" scope="prototype"/>
              else{
                  bean=createBean(beanName,mergedBeanDefinition,args);
              }
             }
        ...
            return bean;
    }
}