详解单例模式及其在Sping中的最优实践

Posted Java鱼仔

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了详解单例模式及其在Sping中的最优实践相关的知识,希望对你有一定的参考价值。

(一)什么是单例模式

在程序中,每new() 一个对象,就会有一个对象实例生成。有时候在程序中,需要有一个在完整运行状态下只需要生成一个的实例,我们把这种实例称为单例。
抽象到设计模式中,这种只生成一个实例的模式就是单例模式(Singleton)。

(二)单例模式的实现

单例模式的实现有很多种方式,归根到底是保证new Class()的操作只做一次,在大多数的实现上,会将需要实现单例的类的构造方法改为private修饰,使得无法通过new命令创建对象实例。

(三)单例模式的代码实现

代码模式有很多种实现方式,主要有饿汉式、懒汉式以及有点特殊的单例注册表(Spring的实现方式)。下面会针对上面的这三种方式各自给出一种实现方式。

3.1 饿汉式

饿汉式简单来说就是在项目启动时就将对象实例创建出来,可以用来做需要执行一次的操作:

public class HungrySingleton 
    public static final HungrySingleton INSTANCE;
    static 
        INSTANCE=new HungrySingleton();
    
    private HungrySingleton()
    

调用时只需要直接调用INSTANCE变量就行:

HungrySingleton instance = HungrySingleton.INSTANCE;

静态代码块饿汉式适合从外部文件中获取数据时使用,我在项目下新建一个info.properties,里面包含一条内容info=hello。饿汉式单例代码如下:

public class HungrySingleton2 
    public static final HungrySingleton2 INSTANCE;
    private String info;
    static 
        Properties properties=new Properties();
        try 
            properties.load(HungrySingleton2.class.getClassLoader().getResourceAsStream("info.properties"));
         catch (IOException e) 
            e.printStackTrace();
        
        INSTANCE=new HungrySingleton2(properties.getProperty("info"));
    
    private HungrySingleton2(String info)
        this.info=info;
    
    public String getInfo() 
        return info;
    
    public void setInfo(String info) 
        this.info = info;
    
    @Override
    public String toString() 
        return "Singleton3" +
                "info='" + info + '\\'' +
                '';
    

    public static void main(String[] args) 
        HungrySingleton2 instance = HungrySingleton2.INSTANCE;
        System.out.println(instance.getInfo());
    

3.2 懒汉式

懒汉式是指当第一次调用时才会生成这个实例,后面再调用就只会使用之前生成的实例。懒汉式是实际编写单例模式代码时用的比较多的方案,下面给出一段十分经典的懒汉式单例模式代码案例:

public class LazySingleton 
    private static volatile LazySingleton Instance;
    private LazySingleton();
    public static LazySingleton getInstance()
        if (Instance==null)
            synchronized (LazySingleton.class)
                if(Instance==null)
                    Instance=new LazySingleton();
                
            
        
        return Instance;
    

在面试中,往往这道题能引出volatile和synchorized这两个知识点。

3.3 单例注册表

我看网上很少有人介绍这种单例模式,单例注册表是Spring中Bean单例的核心实现方案。可以通过一个ConcurrentHashMap存储Bean对象,保证Bean名称唯一的情况下也能保证线程安全。下面是单例注册表的简单实现:

public class RegSingleton 
    private final static Map<String, Object> singletonObjects = new ConcurrentHashMap<>(16);

    private RegSingleton()

    public static Object getInstance(String className)
        if (StringUtils.isEmpty(className)) 
            return null;
        
        if (singletonObjects.get(className) == null) 
            try 
                singletonObjects.put(className, Class.forName(className).newInstance());
             catch (Exception e) 
                e.printStackTrace();
            
        
        return singletonObjects.get(className);
    

新建一个测试类:

public class User 
    private String id;
    public void setId(String id) 
        this.id = id;
    
    public String getId() 
        return id;
    
    @Override
    public boolean equals(Object o) 
        if (this == o) return true;
        if (!(o instanceof User)) return false;
        User user = (User) o;
        return Objects.equals(getId(), user.getId());
    
    @Override
    public int hashCode() 
        return Objects.hash(getId());
    

最后测试单例是否生效:

public class Main 
    public static void main(String[] args) 
        User user1 = (User) RegSingleton.getInstance("com.javayz.singleton.User");
        User user2 = (User) RegSingleton.getInstance("com.javayz.singleton.User");
        System.out.println(user1==user2);
    

(四)单例模式在Spring中的最佳实践

单例模式的最佳实践就是Spring中的Bean了,Spring中的Bean默认都是单例模式,Spring实现单例的方式就采用了单例注册表的方式。

首先在Bean工厂中,如果设置了Spring的Bean模式为单例模式,Spring就会通过getSingleton的方式去获取单例Bean对象。

接着就会进入到DefaultSingletonBeanRegistry类的getSingleton方法中,忽略掉其他代码,只看单例Bean生成相关代码

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry 
    /** Cache of singleton objects: bean name to bean instance. */
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
    public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) 
   	//一系列处理操作
         
         finally 
            if (recordSuppressedExceptions) 
               this.suppressedExceptions = null;
            
            afterSingletonCreation(beanName);
         
         if (newSingleton) 
             //如果实例对象不存在,注册到单例注册表中
            addSingleton(beanName, singletonObject);
         
      
      return singletonObject;
   


对应的addSingleton方法就是将对象加入到单例注册表中:

(五)总结

至此,单例模式的内容差不多就结束了,结合源码看设计模式每次都有新收获。我是鱼仔,我们下期再见!

以上是关于详解单例模式及其在Sping中的最优实践的主要内容,如果未能解决你的问题,请参考以下文章

sping+quartz定时任务的最简单实践

单例模式详解

动态规划及其应用

李超树详解

单例模式详解

单例模式详解