创建型模式:单例模式-Singleton Pattern

单例模式
定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

  • 饿汉式单例模式

    饿汉式单例类,Java实现,代码如下所示:

public class Singleton {  
    private static Singleton singleton = new Singleton(); 

    private Singleton(){}

    public static Singleton getInstance(){  
        return singleton;  
    }  
}

由于每次使用new关键字来实例化Singleton类时都将产生一个新对象,为了确保Singleton实例的唯一性,我们需要禁止类的外部直接使用new来创建对象,因此需要将Singleton的构造函数的可见性改为private。

当类被加载时,静态变量singleton会被初始化,此时类的私有构造函数会被调用,单例类的唯一实例将被创建。


  • 懒汉式单例模式

    懒汉式单例类,Java实现,代码如下所示:

public class Singleton {  
    private static Singleton singleton; 

    private Singleton(){}  

    public static synchronized Singleton getInstance() {  
        if(singleton == null){  
            singleton = new Singleton();  
        }  
        return singleton;  
    }  
} 

懒汉式单例在第一次调用getInstance()方法时实例化,在类加载时并不自行实例化,这种技术又称为延迟加载(Lazy Load)技术,即需要的时候再加载实例,为了避免多个线程同时调用getInstance()方法,我们可以使用关键字synchronized。

问题一:该懒汉式单例类在getInstance()方法前面增加了关键字synchronized进行线程锁,以处理多个线程同时访问的问题。但是,上述代码虽然解决了线程安全问题,但是每次调用getInstance()时都需要进行线程锁定判断,在多线程高并发访问环境中,将会导致系统性能大大降低。如何既解决线程安全问题又不影响系统性能呢?

懒汉式单例类改进,Java实现,代码如下所示:

public class Singleton {  
    private static Singleton singleton; 

    private Singleton(){}  

    public static Singleton getInstance() {  
        if(singleton == null){
            synchronized (Singleton.class) {  
                singleton = new Singleton(); 
            } 
        }  
        return singleton;  
    }  
}

问题一貌似得以解决,事实并非如此。如果使用以上代码来实现单例,还是会存在单例对象不唯一。原因如下:
假如在某一瞬间线程A和线程B都在调用getInstance()方法,此时instance对象为null值,均能通过singleton == null的判断。由于实现了synchronized加锁机制,线程A进入synchronized锁定的代码中执行实例创建代码,线程B处于排队等待状态,必须等待线程A执行完毕后才可以进入synchronized锁定代码。但当A执行完毕时,线程B并不知道实例已经创建,将继续创建新的实例,导致产生多个单例对象,违背单例模式的设计思想,因此需要进行进一步改进,在synchronized中再进行一次(singleton == null)判断,这种方式称为双重检查锁定(Double-Check Locking)。

双重检查锁定实现的懒汉式单例类,Java实现,代码如下所示:

public class Singleton {  
    private volatile static Singleton singleton; 

    private Singleton(){}  

    public static Singleton getInstance() { 
        //第一重判断  
        if(singleton == null){
            //锁定代码块
            synchronized (Singleton.class) {
                //第二重判断 
                if (singleton == null) { 
                    //创建单例实例 
                    singleton = new Singleton(); 
                }
            } 
        }  
        return singleton;  
    }  
}

需要注意的是,如果使用双重检查锁定来实现懒汉式单例类,需要在静态成员变量singleton之前增加修饰符volatile,被volatile修饰的成员变量可以确保多个线程都能够正确处理,且该代码只能在JDK 1.5及以上版本中才能正确执行。由于volatile关键字会屏蔽Java虚拟机所做的一些代码优化,可能会导致系统运行效率降低,因此即使使用双重检查锁定来实现单例模式也不是一种完美的实现方式。

在这里使用volatile会或多或少的影响性能,但考虑到程序的正确性,牺牲这点性能还是值得的。 DCL优点是资源利用率高,第一次执行getInstance时单例对象才被实例化,效率高。缺点是第一次加载时反应稍慢一些,在高并发环境下也有一定的缺陷,虽然发生的概率很小。DCL虽然在一定程度解决了资源的消耗和多余的同步,线程安全等问题,但是他还是在某些情况会出现失效的问题,也就是DCL失效,在《Java并发编程实践》一书建议用静态内部类单例模式来替代DCL。


  • 静态内部类单例模式

    静态内部类单例类,Java实现,代码如下所示:

public class Singleton { 
    private Singleton(){}

    private static class SingletonHolder {  
        private static final Singleton INSTANCE = new Singleton();  
    }

    public static Singleton getInstance() {  
        return SingletonHolder.INSTANCE;  
    } 
} 

  • 饿汉式单例类与懒汉式单例类比较

    1. 饿汉式单例类在类被加载时就将自己实例化,它的优点在于无须考虑多线程访问问题,可以确保实例的唯一性;从调用速度和反应时间角度来讲,由于单例对象一开始就得以创建,因此要优于懒汉式单例。但是无论系统在运行时是否需要使用该单例对象,由于在类加载时该对象就需要创建,因此从资源利用效率角度来讲,饿汉式单例不及懒汉式单例,而且在系统加载时由于需要创建饿汉式单例对象,加载时间可能会比较长。

    2. 懒汉式单例类在第一次使用时创建,无须一直占用系统资源,实现了延迟加载,但是必须处理好多个线程同时访问的问题,特别是当单例类作为资源控制器,在实例化时必然涉及资源初始化,而资源初始化很有可能耗费大量时间,这意味着出现多线程同时首次引用此类的机率变得较大,需要通过双重检查锁定等机制进行控制,这将导致系统性能受到一定影响。

你可能感兴趣的:(Design,Patterns)