单例模式是比较常见的一种模式,下面简单地进行单例模式的总结。
一、概念
单例模式是这样一种概念:该类对象在当前的app中只有唯一一个,而且该对象是全局性的,可以被所有对象访问到。单例模式其实是非常简单的模式,它只要保证我们的系统只是初始化该类对象一次即可,废话不多说,接着下面;
二、如何创建单例?
有单例的概念,我们知道,单例对象必须只能被创建一次,那么,该如何保证只能被创建一次呢?
显然,既然只有一个对象,那么对象应该是static的;
同时为了保证这对象的只读性,我们要将它设置为private,通过get方法获取该实例;
另外,要保证单例只是被创建一次,static修饰是不能保证的,所以我们只有将构造方法设置为私有才能保证这点,然而私有的构造方法只能被类内部方法访问,所有我们的单例对象的引用只能被该类所持有(具体看代码);
具体看下面的示例代码(分为饿汉和懒汉模式,前者是类加载时就初始化单例,后者则在调用get方法时初始化):
饿汉模式创建单例例子
class SingletonDemo1{ //保证对象只是被new一次,我们需要将构造方法私有化 private SingletonDemo1(){ } //构造方法私有化之后,其他类是无法通过new该对象的(暂时不考虑反射),这时,只能在该类本身设置引用变量,指向该单例 //另外,要保证可读性和唯一性,需要以private和static修饰 private static SingletonDemo1 instance = new SingletonDemo1();//加载类时直接初始化,这种模式称为饿汉模式 //提供一个访问单例的全局入口get方法 public static SingletonDemo1 getInstance(){ return instance; } }
上面的懒汉模式中,在类加载时变初始化了对象。有时候,我们不希望对象过早加载(有可能该对象所需要的空间资源较多),想在正真使用时才加载(当然,这样第一次加载时响应速度肯定比不上饿汉模式的),这种模式称为懒汉模式,上代码例子:
//懒汉模式 class SingletonDemo2{ private SingletonDemo2(){ } private static SingletonDemo2 instance = null;//懒汉模式在类加载时不初始化单例 public static SingletonDemo2 getInstance(){ //懒汉模式在正真要用单例的时候初始化单例 if(instance==null){ instance = new SingletonDemo2(); } return instance; } }
总的来说,单例模式的确是没什么难度的。但是,我们上面的程序并没有考虑多线程的并发问题,在并发时,要保证单例类的构造方法只是被执行一次,上面的代码是不行的,这是,就需要将单例进行同步了。
三、多线程下的单例模式
显然,在饿汉模式下,并没有并发问题存在,因为构造方法是在类加载的时候就执行了,所以,下面的讨论只是针对懒汉模式进行;在懒汉模式中,如果存在多条线程同时访问getInstance的情形,则有可能在第一条线程访问到if判断语句时,系统调度了另外一条线程进行单例初始化(假设单例未初始化),那么就会出现单例被多次初始化的现象;
为了避免并发带来的问题,很容易想到下面的同步手段:
//多线程懒汉模式 class SingletonDemo3{ private SingletonDemo3(){ } private static SingletonDemo3 instance = null; public static SingletonDemo3 getInstance(){ //初始化前先对单例进行上锁操作 synchronized(instance){ if(instance==null){ instance = new SingletonDemo3(); } } return instance; } }
上面代码解决了多线程下单例初始化的问题。
然而,我们发现,大多数时候,我们并不需要对对象进行同步操作,同步一般只需在单例初次初始化时就可以了,所以,我们可以进行一下改进(该方法对对象进行两次是否为null的判断,具体看代码注释):
//多线程懒汉模式改进版本 class SingletonDemo4{ private SingletonDemo4(){ } private static SingletonDemo4 instance = null;//懒汉模式在类加载时不初始化单例 public static SingletonDemo4 getInstance(){ //上锁前先判断是否为null if(instance==null){ //初始化前先对单例进行上锁操作 synchronized(instance){ //由于再上锁和if语句之间的空隙时间,有可能某条线程对单例进行了初始化,所以需要再次判断单例是否被初始化 if(instance==null){ instance = new SingletonDemo4(); } } } return instance; } }
到这里,我们就可以保证单例模式的成功而且保证了性能。
当然,其实单例模式中构造方法私有化并不能一定保证对象被创建一次的,因为通过反射还是可以创建对象的,下面加单讨论下如何防止单例中反射的多次创建对象的为题。
四、防止反射机制破坏单例
其实防止反射破坏单例的方法挺简单的,在正式介绍如何实现之前,我们要先理解反射创建对象的原理是什么:其实反射创建对象只是在运行时,通过class对象的方法调用该类的无参构造方法罢了。明白这点就好办了,我们只需要组装class的方法调用构造方法即可,最简单的就是在构造方法中抛出异常即可,下面是简单的代码示例:
//防止反射的单例模式 class SingletonDemo5{ private SingletonDemo5(){ //判断单例是否为null if(instance!=null){ throw new RuntimeException("单例不可多次初始化!"); } } private static SingletonDemo5 instance = null;//懒汉模式在类加载时不初始化单例 public static SingletonDemo5 getInstance(){ //上锁前先判断是否为null if(instance==null){ //初始化前先对单例进行上锁操作 synchronized(instance){ //由于再上锁和if语句之间的空隙时间,有可能某条线程对单例进行了初始化,所以需要再次判断单例是否被初始化 if(instance==null){ instance = new SingletonDemo5(); } } } return instance; } }
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:设计模式–单例模式 - Python技术站