• 深入理解单例模式


    单例模式学习和了解使用场景。

    1.什么是单例模式?
    确保一个类只能创建一个实例。
    2.实现思路是什么?
    不能让外界创建对象,所以构造器要私有化。
    提供获得单例对象的方法。(所以这个方法是公开的,并且这个方法里New出了对象)
    3.实例:
    (1)根据上面的思路我们来写一个类,让其实现单例模式

    public class Singleton{
       private Singleton(){}
       private static Singleton instance =new Singleton(); //这里用static修饰
       public static Singleton getInstance(){
               return instance;
       }
    }

    类加载时就创建对象,不管用不用,对象已经创建好了。线程安全(初始化就把对象创建好了,不会有多个线程创建多个对象的情况)。这种也称之为饿汉模式。

    (2).上面的单例模式在初始化时就创建对象,并且是线程安全的。但是也有缺点,那就是在初始化时就创建了很多实例,并且很多实例可能我们都用不到,这样会占用内存。浪费内存资源(内存是很宝贵的资源)。我们可以考虑在需要这个实例时我们才去创建对象。

    public class Singleton{
      private Singleton(){}
      private static Singleton instance = null ;
      public static Singleton getInstance(){  
    
      //如果还没有这个实例,我们就去创建这个实例。
          if(instance==null){
            instance=new Singleton();    
        }
        return instance;
      }
    }

    这种创建单例的方式叫懒汉模式(懒加载):用到的时候才创建。

    (3).显然上面的第二个方法创建单例是有问题的。想一下,当两个线程同时走到  if(instance==null)这里时,那么就有可能创建了两个实例(其实还可能有更多线程同时走到这里)。那就不是单例的了。怎么办?这时我们可以考虑加锁。我们在获取对象这个方法上加锁,这样就不会有两个线程同时走到if(instance==null)这里了。

    public class Singleton{
      private Singleton(){}
      private static Singleton instance = null ;
    
    //对方法加锁,
      public static synchronized Singleton getInstance(){  
          if(instance == null){
            instance = new Singleton();    
        }
        return instance;
      }
    }

    (4).好像问题已经解决了,但是对方法加锁会有一个问题,那就是我们每次获取实例都需要同步(当已经有单例对象,别人再去获取这个单例时,同样需要等待锁资源),这样会降低程序的性能。其实我们只是想在创建对象的时候保证只有一个实例,那么我们可以把锁的范围缩小,在创建对象那里加锁。

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

    这样获取对象的时候,如果还没有被创建出来,只能有一个线程能创建,保证了单例。如果对象已经存在,那么我们获取对象时不用等待锁了。

    想一想为什么锁里面会再次判断 if(instance==null) ?

    当单例对象不存在,并且两个线程同时走到 synchronized(Singleton.class),这时第一个线程先拿到锁,第二线程会等待锁资源,当第一线程创建了对象并释放了锁资源时,第二个线程会拿到锁资源,如果没有再次判断,那么第二个线程会再次创建对象。所以必须用 if(instance==null) 再次判断。这种方式也叫双重检验锁

    (5).通过注册表方式:

    首先将需要单例的实例通过唯一键注册到注册表,然后通过键来获取单例

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

    上面的单例注册表的方式,其实就是把实例放到了一个map中,key是对象的名字,value就是这个对象。当我们获取这个对象时,根据对象的名字就可以从map获取这个对象了。如果没有这个对象,我们就先把这个对象放到map中。

    问题1:为什么把对象放到map中就可以实现单例?

    map中的key是唯一的,当我们根据key获取value时,获取的对象肯定是唯一的

    问题2:为什么把单例放到map中?

    其实这是用map这种数据结构存放我们实例化了的对象。我们知道new出来的对象都是存在堆栈中的。通过这种方式,我们把单例对象放到了map中,创建对象变成了往map中存数据,获取对象变成了从map中取数据。我们经常说的spring中的Ioc容器其实就是一个map。

    4.应用

    我们知道Spring里面是有单例的,如:<bean id="date" class="java.util.Date" singleton="true"/> 那么spring 里的单例是如何实现的呢?

    本注册表实现了Spring接口“SingletonBeanRegistry”,该接口定义了操作共享的单例对象,Spring容器实现将实现此接口;

    public class SingletonBeanRegister implements SingletonBeanRegistry {  
        //单例Bean缓存池,此处不考虑并发  
        private final Map<String, Object> BEANS = new HashMap<String, Object>();  
        public boolean containsSingleton(String beanName) {  
            return BEANS.containsKey(beanName);  
        }  
        public Object getSingleton(String beanName) {  
            return BEANS.get(beanName);  
        }  
        @Override  
        public int getSingletonCount() {  
            return BEANS.size();  
        }  
        @Override  
        public String[] getSingletonNames() {  
            return BEANS.keySet().toArray(new String[0]);  
        }  
        @Override  
        public void registerSingleton(String beanName, Object bean) {  
            if(BEANS.containsKey(beanName)) {  
                throw new RuntimeException("[" + beanName + "] 已存在");  
            }  
            BEANS.put(beanName, bean);  
      }  
    }  


    5.总结:
    单例模式由于构造器是私有化的,所以不能被继承

  • 相关阅读:
    Centos下Zookeeper的安装部署
    Zookeeper入门
    Redis高可用-主从,哨兵,集群
    Redis入门
    centos7 安装redis6.0.3
    二叉树的遍历及常用算法
    分享一个seata demo,讲两个个问题
    互联网公司,我们需要什么样的中层技术管理以及996和程序员有多大关系?
    Spring Boot微服务如何集成seata解决分布式事务问题?
    软件服务架构的一些感悟
  • 原文地址:https://www.cnblogs.com/inspred/p/8052393.html
Copyright © 2020-2023  润新知