一、什么是单例

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

许多时候一个项目只需要拥有一个全局对象,我们就可以使用单例的思想,这样有利于优化程序运行时所占的内存,也能协调系统整体的行为。

  • 注意:

    1. 单例类只能有一个实例。
    2. 单例类必须自己创建自己的唯一实例。
    3. 单例类必须给所有其他对象提供这一实例。

明确定义和让我们看看代码

二、饿汉式

public class EagerSingletonEx {
    //私有化使外部不能直接调用
    //静态是为了可以直接return
    private static EagerSingletonEx instance = new EagerSingletonEx();

    //私有的无参构造器,使外部不能new对象
    private EagerSingletonEx(){

    }

    //通过getInstance方法来返回一个instance对象
    public static EagerSingletonEx getInstance(){
        return instance;
    }
}

以上就是饿汉式单例,他在类加载时就把单例初始化完成,保证getInstance()的时候,单例是已经存在的了。

饿汉式时线程安全的,这种方式比较常用,因为没有加锁,执行效率会提高,但容易产生垃圾对象。

他在类加载时就初始化,会浪费内存。

三、懒汉式

public class LazySingleton {
    //私有化使外部不能直接调用
    //静态是为了可以直接return
    private static LazySingleton m_instance = null;

    //无参构造器,外部不能new
    private LazySingleton(){

    }

    //getInstance方法种判断一下m_instance是否被创建,创建了则返回已有对象,没创建就创建新对象
    synchronized public static LazySingleton getInstance(){
        if (m_instance==null){
            return new LazySingleton();
        }else {
            return m_instance;
        }
    }
}

懒汉式比较懒,只有当调用getInstance的时候,才会去初始化这个单例。这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,在多线程不能正常工作,所以严格意义上它并不算单例模式。

四、饿汉式和懒汉式对比

1、线程安全:

  • 饿汉式天生就是线程安全的,可以直接用于多线程而不会出现问题,
  • 懒汉式本身是非线程安全的,为了实现线程安全有几种写法。
public class SingletonLHsyn {
    
    private static SingletonLHsyn instance;
    
    private SingletonLHsyn (){}
    
    //在这加了一个synchronized,使它变成了线程安全的
    public static synchronized SingletonLHsyn getInstance() {
        if (instance == null) {
            instance = new SingletonLHsyn();
        }
        return instance;
    }
}

这种方式具备很好的 lazy loading,他在第一次调用才初始化,避免了内存浪费,能够在多线程中很好的工作。

但是,效率很低,必须加锁 synchronized 才能保证单例,但加锁会影响效率。

不过getInstance() 的性能对应用程序不是很关键(因为该方法使用不太频繁)。

2、资源加载和性能:

饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成。

而懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的工作比较多,性能上会有些延迟,之后就和饿汉式一样了。

五、Spring中的单例实现

public class ResSingleton {
    
    private static HashMap map = new HashMap();

    //静态块,在类被加载时自动执行
    static {
        ResSingleton singleton = new ResSingleton();
        //key==类名  value==ResSingleton对象
        map.put(singleton.getClass().getName(), singleton);
    }

    //受保护的默认构造函数,如果为继承关系,则可以调用,克服了单例类不能为继承的缺点  
    protected ResSingleton(){}

    public static ResSingleton getInstance(String name){
        
        //判断当前传入的name值是否为null
        if (null==name){
            //是的话就将类名给他
            name = ResSingleton.class.getName();
        }
        
        //如果key不是null,检查一下value有没有值
        //不为null就说明在map中之前创建过这个实例,直接返回这个实例。
        if (null == map.get(name)){
            try {
                //如果是空就使用反射创建一个实例
                //此时key为传入的name,value为这个类名对应的类
                map.put(name, Class.forName(name).newInstance());

            }catch (Exception e){
                System.out.println("Error happened!\n" + e.getMessage());
            }
        }
        //返回实例
        return (ResSingleton) map.get(name);
    }

}

简单地说就是你给我一个类名,我去Map中根据你给我的名字查找,有实例我就直接给你,没有对应的实例我就使用反射创建一个实例给你。

它其实就是工厂模式的一种,帮你管理实例的产生。

Last modification:October 24th, 2020 at 02:52 pm
如果觉得我的文章对你有用,请随意赞赏