美文网首页
单例模式

单例模式

作者: y三小石 | 来源:发表于2018-10-25 22:37 被阅读0次

    一、定义

    单例模式:单例模式是一种常用的软件设计模式,其定义是单例对象的类只能允许一个实例存在。

    二、单例模式结构图

    单例模式

    三、单例模式的实现

    懒汉式,线程不安全

    public class Singleton {
        private static Singleton instance;
        private Singleton (){}
        public static Singleton getInstance() {
         if (instance == null) {
             instance = new Singleton();
         }
         return instance;
        }
    }
    

    使用了懒加载模式,但是却存在致命的问题。当有多个线程并行调用 getInstance() 的时候,就会创建多个实例。也就是说在多线程下不能正常工作。

    懒汉式,线程安全

    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
    

    虽然做到了线程安全,并且解决了多实例的问题,但是它并不高效。

    双重检验锁

    public static Singleton getSingleton() {
        if (instance == null) {                         //Single Checked
            synchronized (Singleton.class) {
                if (instance == null) {                 //Double Checked
                    instance = new Singleton();
                }
            }
        }
        return instance ;
    }
    

    这段代码看起来很完美,很可惜,它是有问题。主要在于instance = new Singleton()这句,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情。

    1.给 instance 分配内存
    2.调用 Singleton 的构造函数来初始化成员变量
    3.将instance对象指向分配的内存空间(执行完这步 instance 就为非 null 了)

    但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。

    我们只需要将 instance 变量声明成 volatile 就可以了。

    public class Singleton {
        private volatile static Singleton instance; //声明成 volatile
        private Singleton (){}
        public static Singleton getSingleton() {
            if (instance == null) {                         
                synchronized (Singleton.class) {
                    if (instance == null) {       
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
       
    }
    

    饿汉式

    public class Singleton{
        //类加载时就初始化
        private static final Singleton instance = new Singleton();
        
        private Singleton(){}
        public static Singleton getInstance(){
            return instance;
        }
    }
    

    缺点是它不是一种懒加载模式(lazy initialization),单例会在加载类后一开始就被初始化.

    静态内部类

    public class Singleton {  
        private static class SingletonHolder {  
            private static final Singleton INSTANCE = new Singleton();  
        }  
        private Singleton (){}  
        public static final Singleton getInstance() {  
            return SingletonHolder.INSTANCE; 
        }  
    }
    
    

    静态内部类也是一种懒加载模式,因为静态内部类只有在使用的时候才会被加载。(见https://blog.csdn.net/zmx729618/article/details/69227762
    《Effective Java》推荐的。

    四、使用的案例

    Spring IOC 注入的bean默认都是单例的。对无状态的类是可以用单例的。

    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定义判断,此判断依据通常来自于组件配置文件的单例属性开关  
                //<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;  
        }  
        }
    

    四、单例的好处

    1.为什么用单例:
    我发现所有可以使用单例模式的类都有一个共性,那就是这个类没有自己的状态,换句话说,这些类无论你实例化多少个,其实都是一样的,而且更重要的一点是,这个类如果有两个或者两个以上的实例的话,我的程序竟然会产生程序错误或者与现实相违背的逻辑错误。
    2.优点:
    ● 在内存中只有一个对象,节省内存空间;
    ● 避免频繁的创建销毁对象,可以提高性能;
    ● 避免对共享资源的多重占用,简化访问;
    ● 为整个系统提供一个全局访问点。

    相关文章

      网友评论

          本文标题:单例模式

          本文链接:https://www.haomeiwen.com/subject/jfnctqtx.html