单例模式(Singleton)
单例模式(Singleton),保证一个类仅有一个实例,并提供一个访问它的全局访问点。
通常我们可以让一个全局变量使得一个对象被访问,但它不能防止你实例化多个对象。一个最好的办法就是:让类自身负责保存它的唯一实例。这个类可以保证没有其他实例可以被创建,并且它可以提供一个访问该实例的方法。
单例模式结构图

单例模式代码结构
单例模式经典实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| 单例模式代码结构 class Singleton { private static Singleton instance; private Singleton() {
} public static Singleton GetInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
Singleton s1 = Singleton.GetInstance(); Singleton s2 = Singleton.GetInstance(); if (s1 == s2) Console.WriteLine("两者相同"); else Console.WriteLine("两者不同");
|
多线程下的单例模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 多线程时的单例模式 class SingletonSync { private static SingletonSync instance; private static readonly object syncRoot = new object(); private SingletonSync() {
} public static SingletonSync GetInstance() { lock (syncRoot) { if (instance == null) instance = new SingletonSync(); } return instance; } }
|
双重锁单例模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
class SingletonDoubleSync { private static SingletonDoubleSync instance; private static readonly object SyncRoot = new object(); private SingletonDoubleSync() {
} public static SingletonDoubleSync GetInstance() { if (instance == null) { lock (SyncRoot) { if (instance == null) { instance = new SingletonDoubleSync(); } } } return instance; } }
可不可以将内部的instance==null判断删除?
不可以,比如说,有两个instance均为null的线程同时访问单例类,它们都可以通过第一重instance==null的检查,待第一个线程执行完后,第一个线程获取到了实例,若此时没有instance==null的内层判断,第二个线程可以紧接第一个线程后获取到实例。
|
单例模式总结
客户端不必再考虑是否需要去实例化的问题,而把责任都给了应该负责的类去处理。这涉及了设计模式中的单例模式。实例化与否的过程应该由类自身来判断,而不是别人的责任,别人只是要使用它。
单例模式解决的两个基本问题:全局访问和实例化控制。
单例模式使用的场景
- 需要频繁的进行创建和销毁的对象
- 创建对象时耗时过多或耗费资源过多(重量级对象)
- 经常用到的对象、工具类对象、频繁访问数据库或文件的对象(数据源、session工厂)
- 比如说,unity设计游戏中GameManager、AudioManager
如何维护单一的实例?不被外界new?
所有类都有构造方法,不编码系统默认生成空的构造方法,若有显示定义的构造,则默认构造会失效。
只需要将构造方法变成private的,那么外部程序就不能new它了。
那么如何调用这个单一的实例呢?
书写public的方法返回类的实例,类的构造过程在类中实现。
对唯一实例的受控访问
单例模式因为Singleton类封装它的唯一实例,这样它可以严格地控制客户怎么样访问它以及何时访问它。简单地说就是对唯一实例的受控访问。
多线程时候的单例模式
多个线程同时访问Singleton类调用GetInstance方法,会有可能造成产生多个实例。
可以给进程加锁Lock来处理。
Lock是确保当一个线程位于代码段临界区时,另一个线程不进入临界区。如果其他的线程试图进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。
也就是说,Lock可以确保前面占用代码的一个线程先跑完,再使下一个要获取该锁的线程开始,否则下一个要跑该段代码段等待。
饿汉式

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| class Singleton { private Singleton() { } private final static Singleton instance = new Singleton(); public static Singleton getInstance() { return instance; } }
class Singleton { private Singleton() {} private static Singleton instance; static { instance = new Singleton(); } public static Singleton getInstance() { return instance; } }
|
懒汉式
所谓懒汉式,就是在需要调用的时候再创建类的实例。
线程不安全的懒汉式单例
1 2 3 4 5 6 7 8 9 10
| class Singleton { private static Singleton instance; private Singleton() {} public static Singleton getInstance() { if(instance == null) { instance = new Singleton(); } return instance; } }
|
线程安全的懒汉式单例
上面线程不安全,那上锁不就好了,使用synchronized关键字。
这样虽然解决了线程安全,但其实实例化操作只做一次,而获取实例
(即getInstance)的操作是很多次的,把调用的方法加上同步,会大大降低效率。
1 2 3 4 5 6 7 8 9 10 11
| class Singleton { private static Singleton instance; private Singleton() {} public static synchronized Singleton getInstance() { if(instance == null) { instance = new Singleton(); } return instance; } }
|
双重检查多线程安全单例模式
上面代码效率低,那在同步前判断一下有没有实例化不就好了,若没有实例化则用同步方法new一个,否则直接return即可。即所谓的双重检查。 需要用到关键字volatile,防止指令重排。如果不用volatile关键字,就会和线程不安全情形一样,在if判断那会有并发。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class Singleton { private static volatile Singleton instance; private Singleton() {} public static synchronized Singleton getInstance() { if(instance == null) { synchronized (Singleton.class) { if(instance == null) { instance = new Singleton(); } } } return instance; } }
|
静态内部类单例模式实现
静态内部类在外部类装载时不会实例化,当调用的时候才会装载并实例化,且JVM保证了其装载时的线程安全性。也能保证懒加载和线程安全,有点像自带版的双重检查。
1 2 3 4 5 6 7 8 9 10 11 12
| class Singleton { private static volatile Singleton instance; private Singleton() {} private static class SingletonInstance { private static final Singleton INSTANCE = new Singleton(); } public static synchronized Singleton getInstance() { return SingletonInstance.INSTANCE; } }
|
枚举实现的单例模式
其实,使用枚举也能实现单例模式,不仅能避免多线程同步问题,也能防止反序列化重新创建新的对象。
1 2 3 4 5 6
| enum Singleton { INSTANCE; public void say() { System.out.println("好哎"); } }
|