1:主题拆解
①单例模式(单线程和多线程)
②单例模式的三种写法
③单例模式的优缺点
④单例模式应用场景
2:基本介绍
一句话描述:某个类只能有一个实例,提供一个全局的访问点。
单例模式比较简单,可以说没有复杂的调用和接口的设计,就是一个简单的类,只是要求这个类只生成一个对象,无论什么时候都要保证这一点,因此只能生成一个实例的模式就是单例模式。3:如何手写一个单例模式
单例模式:保证进程中,类型只有一个实例
①构造函数私有化,防止他人实例化
②对外提供一个获取实例的途径,公开的静态方法
③返回共用一个静态字段
方法一
第一版
public class Singleton
{
private Singleton()
{
//构造函数私有化
}
private static Singleton _Singleton = null;
public static Singleton CreateInstance()
{
if (_Singleton == null)
{
_Singleton = new Singleton();
}
return _Singleton;
}
}
分析:如果对于多线性的访问,此处的会出现类被多次构造的问题,因此需要继续进行改进,加锁用于解决多线程访问的问题
第二版
public class Singleton
{
private Singleton()
{
//构造函数私有化
}
private static Singleton _Singleton = null;
private static readonly object Singleton_Lock = new object();
public static Singleton CreateInstance()
{
lock (Singleton_Lock)
{
if (_Singleton == null)
{
_Singleton = new Singleton();
}
}
return _Singleton;
}
}
分析:开始多线程初始化,lock锁定,判断对象创建。
但是当类实例化结束之后,再来多个线程请求的时候都需要等待锁。
这无疑是对性能的一种损耗与耗时。因此我们继续做优化
第三版
public class Singleton
{
private Singleton()
{
//构造函数私有化
}
private static Singleton _Singleton = null;
private static readonly object Singleton_Lock = new object();
public static Singleton CreateInstance()
{
//对应已经创建,不需要等待锁,直接返回
if (_Singleton == null)
{
lock (Singleton_Lock)//可以保证任意时刻只有一个线程进入,其他线程等待
{
if (_Singleton == null)//这个判断不能去掉,保证只初始化一次
{
_Singleton = new Singleton();
}
}
}
return _Singleton;
}
}
分析:
这里的两次判断,第一判断:效率,第二判断:避免同步。之所以这样是因为避免加锁后,再次加锁。大大增强了执行效率。
方式二
public sealed class SingletonSecond
{
private SingletonSecond()
{
//构造函数私有化
}
private static SingletonSecond _SingletonSecond = null;
static SingletonSecond()
{
_SingletonSecond = new SingletonSecond();
}
public static SingletonSecond CreateInstance()
{
return _SingletonSecond;
}
}
分析:静态构造函数,由CLR保证,在第一次使用到这个类型之前,自动被调用且只调用一次
方式三
public sealed class SingletonThird
{
private SingletonThird()
{
}
private static SingletonThird _SingletonThird = new SingletonThird();
public static SingletonThird CreateInstance()
{
return _SingletonThird;
}
}
分析:静态字段:由CLR保障,在第一次使用这个类型之前,会自动初始化且只初始化一次
4:实现方法分析
实现方法分为懒汉式与饿汉式,这个比喻很形象,就好比一个抠脚大汉看待吃饭这件事。
懒汉式可以理解为抠脚大汉饿出生命危险,不吃不行的时候才开始吃饭。
饿汉式可以理解为抠脚大汉只要遇到与吃饭相关的事情,先不管,赶紧开吃。
我们放到上面的三个版本中可以分析得出,
方法一为懒汉式
懒汉式方法总是会出现这样或那样的问题的,因为考虑到了多线程机制,实现起来比较麻烦,并且还会出现问题,就算是使用了一定的解救办法(同步、加锁、双重判断)的办法,性能还是被损耗了,因此懒汉式方法的不推荐使用。
方式二、三为饿汉式
static修饰的方法和属性,所以是在类加载的时候被创建,后期不会再改变,所以线程是安全的。
5:单例模式的优缺点
1:优点
①活动的单例只有一个实例,对单例类的所有实例化得到的都是相同的一个实例。这样就防止其它对象对自己的实例化,确保所有的对象都访问一个实例。
②提供了对唯一实例的受控访问。
③由于在系统内存中只存在一个对象,因此可以节约系统资源,当需要频繁创建和销毁的对象时单例模式无疑可以提高系统的性能。
2:缺点
①不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。
②由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。
③滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。
6:单例模式的使用场景
数据库连接池
线程池
流水号生成器
配置文件读取
IOC容器实例
7:小结
①当一个类的对象只需要或者只可能有一个时,应该考虑单例模式。
②如果一个类的实例应该在初始化时被创建出来,应该考虑使用饿汉式。
③如果一个类的实例不需要预先被创建,也许这个类的实例并不一定能用得上,也许这个类的实例创建过程比较耗费时间,也许就是真的没必要提前创建。那么应该考虑懒汉式。
④在使用懒汉式单例的时候,应该考虑到线程的安全性问题。