单例模式的定义如下:
单例模式(Singleton Pattern):确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类成为单例类,它提供全局访问的方法。单例模式是一种对象创建型模型。
单例模式的分类:
饿汉单例
懒汉单例
单线程单例
多线程单例
饿汉单例:
public class EagerSingleton { // 由于在定义变量的时候实例化单例类,在类加载时就已经创建了单个对象。 private static EagerSingleton instance = new EagerSingleton(); private EagerSingleton() { } public static EagerSingleton GetInstance() { return instance; } }
懒汉单例:
//在第一次调用GetInstance()方法时实例化,在类加载并不自行实例化。这种技术叫做延迟加载(Lazy Load),就是在需要的时候在加载实例。 //为了避免多线程调用GetInstance方法。加lock public class LazySingleton { private static LazySingleton instance = null; private LazySingleton() { } public static LazySingleton GetInstance() { if(instance == null) { new LazySingleton(); } return instance; } }
单线程单例模式:
public class SingleThreadSingleton { private static SingleThreadSingleton instance = null; private SingleThreadSingleton() { } public static SingleThreadSingleton GetInstance() { if(instance == null) { instance = new SingleThreadSingleton(); } return instance; } }
多线程单例:
public class MultiThreadSingleton { private static MultiThreadSingleton instance; private static readonly object syncRoot = new object();//静态的只读的辅助对象 private MultiThreadSingleton() { } public static MultiThreadSingleton GetInstance() { lock (syncRoot) // 同一时刻枷锁的这部分程序只有一个线程进入。但是有一个问题这样会影响效率 { if(instance == null) { instance = new MultiThreadSingleton(); } } return instance; } }
多线程实例改良版: (volatile的作用和用法详见-C#学习——volatile关键字)
public class MultiThreadSingleton { // volatile 保证严格意义的多线程编译器在代码编译时对指令不进行微调 private static volatile MultiThreadSingleton instance = null; private static readonly object syscRoot = new object(); private MultiThreadSingleton() { } public static MultiThreadSingleton GetInstance() { if(instance == null)// 多个线程调用GetInstance方法时,都可以通过第一重if(instance == null),可以提升效率 { lock (syscRoot) //只有一个线程时进入 { if(instance == null) // 如果没有这层,第一个线程创建了实例,第二个线程还可以创建实例。 { instance = new MultiThreadSingleton(); } } } return instance; } }
静态单例模式:
public class StaticSingleton { public static readonly StaticSingleton instance = new StaticSingleton(); private StaticSingleton() { } }
public class StaticSingleton { public static readonly StaticSingleton instance; private StaticSingleton() { } static StaticSingleton() { instance = new StaticSingleton(); } }
饿汉单例与懒汉单例类比较
饿汉单例在类被加载时就将自己实例化,它的优点在于无需考虑多线程访问问题,可以保证实例的唯一性;从调用速度和反应时间角度来讲,由于单例对象一开始就得以创建,因此要优于懒汉式单例。但是无论系统在运行时是否需要使用该单例对象,由于在类加载时该对象就需要创建,因此从资源利用效率角度来讲,饿汉式单例不及懒汉式单例,而且在系统加载时由于需要创建饿汉式单例对象,加载时间可能会比较长。
懒汉式单例类在第一次使用时被创建,无需一直占用系统资源,实现了延迟加载,但是必须处理好多线程同时访问的问题,特别是当单例类作为资源控制器,在实例化时必然涉及资源初始化,而资源初始化很有可能耗费大量时间,这意味着出现多线程同时首次引用此类的几率变得较大,需要通过双重锁定机制进行控制,这将导致系统性能受到影响。
一种更好的单例实现方法
饿汉式单例不能实现延迟加载,不管将来用不用始终占据内存;懒汉式单例类线程安全控制繁琐,而且性能受影响。可见无论是饿汉式单例还是懒汉式单例都存在这样那样的问题,有没有一种方法,能够将两种单例的缺点都克服,而将两者的优点合二为一呢?答案是:Yes!下面我们学习一种更好的被称之为Initialization Demand Holder(初始化需求持有人 loDH)的技术。
在loDH中,我们在单例中增加了一个静态(static)内部类,在该内部类中创建单例对象,再将该单例对象通过getInstance()方法返回给外部使用,实现代码如下所示:
public class Singleton { private Singleton() { } private static class HolderClass { public static Singleton instance = new Singleton(); } public static Singleton GetInstance() { return HolderClass.instance; } } public class Program { public static void Main(string[] args) { Singleton s1, s2; s1 = Singleton.GetInstance(); s2 = Singleton.GetInstance(); Console.WriteLine(s1 == s2); } }
编译并运行上述代码,运行结果为:True,即创建的对象s1和s2为同一对象。由于静态单例对象没有作为Singleton的成员变量进行直接实例化,因此类加载时不会实例化Singleton,第一次调用getInstance()时将加载内部类HolderClass,该内部中类定义了一个static类型的变量instance,此时会首先初始化这个成员变量,由C#公共运行时(Java中是Java虚拟机)来保证其线程安全性,确保该成员变量只能初始化一次。由于getInstance()方法没有任何线程锁定,因此其性能不会造成任何影响。