单例模式是一种比较简单的设计模式,简单来说,就是确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
单例模式特点:
1)单例类只能有一个实例。
2)单例类必须自己创建自己的唯一实例。
3)单例类必须给所有其他对象提供这一实例。
类型:创建类模式
类图:
图1 单例模式类图
注:类图知识点:
1.类图分为三部分,依次是类名、属性、方法
2.以<<开头和以>>结尾的为注释信息
3.修饰符+代表public,-代表private,#代表protected,什么都没有代表包可见。
4.带下划线的属性或方法代表是静态的。
关键点:
1)私有的构造方法
2)指向自己实例的私有静态引用
3)以自己实例为返回值的静态的公有的方法
单例模式的优点:
- 在内存中只有一个对象,节省内存空间。
- 避免频繁的创建销毁对象,可以提高性能。
- 避免对共享资源的多重占用。
- 可以全局访问。
适用场景:
- 需要频繁实例化然后销毁的对象。
- 创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
- 有状态的工具类对象。
- 频繁访问数据库或文件的对象。
- 所有要求只有一个对象的场景。
注意事项:
- 只能使用单例类提供的方法得到单例对象,不要使用反射,否则将会实例化一个新对象。
- 不要做断开单例类对象与类中静态引用的危险操作。
- 多线程使用单例使用共享资源时,注意线程安全问题。
常用方式:
根据实例化对象时机的不同分为两种:
1、饿汉式单例:在单例类被加载时候,就实例化一个对象交给自己的引用;
饿汉式(JAVA)
public class Singleton { private static Singleton singleton = new Singleton(); private Singleton(){} //静态工厂方法 public static Singleton getInstance(){ return singleton; } }
2、懒汉式单例:在调用取得实例方法的时候才会实例化对象。
懒汉式(JAVA)
public class Singleton { private static Singleton singleton; private Singleton(){} public static synchronized Singleton getInstance(){ if(singleton==null){ singleton = new Singleton(); } return singleton; } }
C#
/// <summary> /// 单例模式的实现 /// </summary> public class Singleton { // 定义一个静态变量来保存类的实例 private static Singleton uniqueInstance; // 定义私有构造函数,使外界不能创建该类实例 private Singleton(){ } /// <summary> /// 定义公有方法提供一个全局访问点,同时你也可以定义公有属性来提供全局访问点 /// </summary> /// <returns></returns> public static Singleton GetInstance() { // 如果类的实例不存在则创建,否则直接返回 if (uniqueInstance == null) { uniqueInstance = new Singleton(); } return uniqueInstance; } }
若考虑线程安全(一个类或者程序所提供的接口对于线程来说是原子操作,或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题,那就是线程安全的。)问题,可对getInstance()方法进行改造,有下列三种方式:
1)在getInstance方法上加同步
JAVA
public static synchronized Singleton getInstance() { if (single == null) { single = new Singleton(); } return single; }
C#
/// <summary> /// 单例模式的实现 /// </summary> public class Singleton { // 定义一个静态变量来保存类的实例 private static Singleton uniqueInstance; // 定义一个标识确保线程同步 private static readonly object locker = new object(); // 定义私有构造函数,使外界不能创建该类实例 private Singleton() { } /// <summary> /// 定义公有方法提供一个全局访问点,同时你也可以定义公有属性来提供全局访问点 /// </summary> /// <returns></returns> public static Singleton GetInstance() { // 当第一个线程运行到这里时,此时会对locker对象 "加锁", // 当第二个线程运行该方法时,首先检测到locker对象为"加锁"状态,该线程就会挂起等待第一个线程解锁 // lock语句运行完之后(即线程运行完之后)会对该对象"解锁" lock (locker) { // 如果类的实例不存在则创建,否则直接返回 if (uniqueInstance == null) { uniqueInstance = new Singleton(); } } return uniqueInstance; } }
2)双重检查锁定
JAVA
public static Singleton getInstance() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; }
C#
/// <summary> /// 单例模式的实现 /// </summary> public class Singleton { // 定义一个静态变量来保存类的实例 private static Singleton uniqueInstance; // 定义一个标识确保线程同步 private static readonly object locker = new object(); // 定义私有构造函数,使外界不能创建该类实例 private Singleton() { } /// <summary> /// 定义公有方法提供一个全局访问点,同时你也可以定义公有属性来提供全局访问点 /// </summary> /// <returns></returns> public static Singleton GetInstance() { // 当第一个线程运行到这里时,此时会对locker对象 "加锁", // 当第二个线程运行该方法时,首先检测到locker对象为"加锁"状态,该线程就会挂起等待第一个线程解锁 // lock语句运行完之后(即线程运行完之后)会对该对象"解锁" // 双重锁定只需要一句判断就可以了 if (uniqueInstance == null) { lock (locker) { // 如果类的实例不存在则创建,否则直接返回 if (uniqueInstance == null) { uniqueInstance = new Singleton(); } } } return uniqueInstance; } }
3)静态内部类
JAVA
public class Singleton { private static class LazyHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton (){} public static final Singleton getInstance() { return LazyHolder.INSTANCE; } }
以上三种方式实现区别:
第1种,在方法调用上加了同步,虽然线程安全了,但是每次都要同步,会影响性能,毕竟99%的情况下是不需要同步的;
第2种,在getInstance中做了两次null检查,确保了只有第一次调用单例的时候才会做同步,这样也是线程安全的,同时避免了每次都同步的性能损耗;
第3种,利用了classloader的机制(双亲委托模型)来保证初始化instance时只有一个线程,所以也是线程安全的,同时没有性能损耗,所以一般建议使用这一种。
饿汉式和懒汉式区别:
1、线程安全:
饿汉式天生就是线程安全的,可以直接用于多线程而不会出现问题;懒汉式本身是非线程安全的,为了实现线程安全有几种写法,分别是上面的1、2、3,这三种实现在资源加载和性能方面有些区别。
2、资源加载和性能:
饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成,而懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的工作比较多,性能上会有些延迟,之后就和饿汉式一样了。
在java中,饿汉式单例要优于懒汉式单例。C#中则一般使用懒汉式单例。
懒汉式双重检查锁定方式举例:
JAVA:
package Demo; //懒汉式 public class Test { String input = null; private Test() { } private static volatile Test instance = null; //双重检查锁定的方式 public static Test getInstance() { if (instance == null) { synchronized (Test.class) { if (instance == null) { instance = new Test(); } } } return instance; } public String getInput() { return input; } public void setInput(String input) { this.input = input; } public void printInfo() { System.out.println(input); } } package Demo; public class SingletonDemo { public static void main(String[] args) { // TODO Auto-generated method stub Test a = Test.getInstance(); a.setInput("chen"); Test b = Test.getInstance(); b.setInput("cll"); a.printInfo(); b.printInfo(); if(a == b){ System.out.println("同一个实例"); }else{ System.out.println("不是同一个实例"); } } }
输出结果:
C#
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace SingletonDemo { public class Test { String input = null; private Test() { } private static Test instance = null; // 定义一个标识确保线程同步 private static readonly object locker = new object(); //双重检查锁定的方式 public static Test getInstance() { if (instance == null) { lock (locker) { if (instance == null) { instance = new Test(); } } } return instance; } public String getInput() { return input; } public void setInput(String input) { this.input = input; } public void printInfo() { Console.WriteLine(input); } } } using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace SingletonDemo { class Program { static void Main(string[] args) { Test a = Test.getInstance(); a.setInput("chen"); Test b = Test.getInstance(); b.setInput("cll"); a.printInfo(); b.printInfo(); if(a == b){ Console.WriteLine("同一个实例"); }else{ Console.WriteLine("不是同一个实例"); } } } }
输出结果:
结论:由输出结果可以得知单例模式为一个面向对象的应用程序提供了对象惟一的访问点,不管它实现何种功能,整个应用程序都会共享一个实例对象。