1. 定义
为了确保一个类有且仅有一个实例,而且自行实例化并向整个系统提供这个实例。
2. 使用场景
确保某个类有且只有一个对象的场景,避免产生多个对象消耗过多的资源,或者某种类型的对象只应该有且只有一个。例如,创建一个对象需要消耗的资源过多,如要访问IO和数据库等资源,这时候就需要考虑使用单例模式。
3. 机制
创建一个独一无二觉得的对象,有多种方式。但是不管你如何创建一个单例对象,都必须要确保其他开发人员不能创建该单例对象的新的实例。
那么如何做到这一点呢? 答:为了避免其他开发人员实例化你定义的类,可以创建唯一一个构造函数,并将其设置为私有的访问权限。注意,如果创建了其他非私有的构造函数,或者没有创建任何构造函数,其他对象都能够实例化改类。
设计一个单例类的时候,需要确定何时实例化该类的单例对象。一种做法是创建这个类的实例,并将它作为该类的静态成员变量。例如:
private static Factory factory = new Factory();
然后通过一个公共的getFactory()静态方法获得该类的唯一实例。实例代码:
public class Factory{ private static Factory factory = new Factory (); public static Factory getFactory() { return factory; } private Factory() {} }
如果不希望提前创建单例实例,还可以在第一次需要的时候,延迟初始化它。
if (factory == null) factory = new Factory();
延迟不延迟有哪些方面的区别呢? 为什么实例化还有延迟和非延迟? 一般延迟实例化对象有两个原因: 1. 在静态初始化的时候,没有足够的信息对单例对象进行初始化。 2. 选择延迟初始化单例对象与获取资源有关,如数据库连接,没有使用的需求,就没有必要实例化该单例对象。
3. 单例与线程
如果想在多线程环境下延迟初始化一个单例模型,必须避免多个线程同时初始化该单例对象。在多线程环境下,无法保证在其他线程开始执行该方法时,当前线程已经完整的执行完该方法。这可能出现两个线程同时初始化一个单例对象的情况。为了避免这种情况,需要使用双重校验锁机制(Double Check Lock)去协调不同线程对同一方法的执行。
public class SingletonClass { private static SingletonClass instance = null; public static SingletonClass getInstance() { if(instance==null) { synchronized(SingletonClass.class) { if(instance==null) { instance=new SingletonClass(); } } } return instance; } private SingletonClass() {} }
单例模式或许是最负盛名的一个设计模式,但是很容易被误用,不要让单例作为创建全局变量的一种花哨方法。因为单例会引入耦合,应减少使用单例模式的类的数量。最好的方式是:类只知道与它协作的对象,不必了解它所需要的限制。 需要注意的是:对象具有唯一性,不代表使用了单例模式。
4. 推荐使用的单例模式实现方式
在应用单例模式时,我们知道懒汉模式、饿汉模式、双重检验锁模式。这些实现方式都有各自的特点和缺陷,其中双重校验锁算性是应用最为广泛的,但是也不是完美的。《Java并发编程实践》这本书给出来一个推荐的实现方式:
public class Singleton { // 构造函数 private Singleton() {} public static Singleton getInstance(){ return SingletonHloder.sInstance; } // 静态内部类 private static class SingletonHloder { private static final Singleton sInstance = new Singleton(); } }
实现方式解读:
当第一次加载Singletion类时并不会初始化sInstance,只有在第一次调用Singleton的getInstance方法时才会导致sInstance被初始化。因此,第一次调用getInstance方法会导致虚拟机加载SingletonHolder类,这种方式不仅能够确保线程安全,也能保证单例对象的唯一性,同时也延迟了单例的实例化,所以这是推荐使用的单例模式的实现方式。