什么是单例模式
单例模式是一种经典的,常用的。常考的软件设计模式,也是设计模式中最简单的形式之一。
它的核心结构中只包含一个被称为单例的特殊类,即单例模式可以保证系统中,应用该模式的一个类只有一个对象实例。因此这一模式的目的就是使得类的一个对象成为系统中的唯一实例。
设计模式就是编程大佬们针对一些经典的常见场景,总结的一些对应的解决方案。
单例模式的特点
- 一个类只能有一个实例
- 自己创建这个实例
- 整个系统都要使用这个实例
单例模式的优点
一、实例控制
单例模式会阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例。
二、灵活性
因为类控制了实例化过程,所以类可以灵活更改实例化过程。
单例模式的缺点
一、开销
虽然数量很少,但如果每次对象请求引用时都要检查是否存在类的实例,将仍然需要一些开销。可以通过使用静态初始化解决此问题。
二、可能的开发混淆
使用单例对象(尤其在类库中定义的对象)时,开发人员必须记住自己不能使用new关键字实例化对象。因为可能无法访问库源代码,因此应用程序开发人员可能会意外发现自己无法直接实例化此类。
三、对象生存期问题
不能解决删除单个对象的问题。在提供内存管理的语言中(例如基于.NET Framework的语言),只有单例类能够导致实例被取消分配,因为它包含对该实例的私有引用。在某些语言中(如 C++),其他类可以删除对象实例,但这样会导致单例类中出现悬浮引用。
实现单例模式
实现单例模式有两种形式,饿汉方式和懒汉方式。
什么是饿汉?什么是懒汉?
概念不太好理解,我举个例子大家应该就能明白了。
这里是个洗碗的例子。
第一种:吃完饭立刻洗碗因为下顿饭好了的时候可以立即吃到饭,这是饿汉方式:时刻准备着
第二种:吃完饭把碗放下,等吃下顿饭的时候再去洗碗,这是懒汉方式:用的时候再准备
有没有发现二者的区别呢?
其实饿汉模式的核心思想是立即加载,即在使用类的时候已经将对象创建完毕(不管以后会不会使用到该实例化对象,先创建了再说,很着急的样子);
而懒汉模式的核心思想是“延时加载”,即在调用get()方法时实例才被创建(先不急着实例化出对象,等要用的时候才给你创建出来。不着急),从而也能够优化服务器的启动速度。
懒汉模式与线程中的写实拷贝有异曲同工之妙,写实拷贝也提高了创建子进程的速度。
饿汉方式
public class Singleton {
// 将自身实例化对象设置为一个属性,并用static、final修饰5同时提前准备好
private static final Singleton instance = new Singleton();
// 构造方法私有化
private Singleton() {}
// 静态方法返回该实例
public static Singleton getInstance() {
return instance;
}
}
/* 第二种写法*/
template<typename T> //使用模板类
class Singleton{
private static T data; //提前准备好数据data
public:
static T* getInstance() //提供外部调用方法
{
return &data;
}
};
通过上面Singleton这个被包装好的类,我们可以保证在一个进程中只能有一个T对象的实例。
“饿汉模式”的优缺点:
优点:实现起来简单,没有多线程同步问题。
缺点:当类SingletonTest被加载的时候,会初始化static的对象,静态变量被创建并分配内存空间,从这以后,这个static的instance对象便一直占着这段内存(即便你还没有用到这个实例),当类被卸载时,静态变量被摧毁,并释放所占有的内存,因此在某些特定条件下会耗费内存。
懒汉方式
template<typename T>
class Singleton{
//将自身实例化对象设置为一个属性,并用static修饰
private static T* inst;
public:
//静态方法返回该实例
static T* GetInstance()
{
if(inst==NULL)
{
inst=new T(); //用的时候才new一个对象
}
return inst;
}
};
“懒汉模式”的优缺点:
优点:实现起来比较简单,当类SingletonTest被加载的时候,静态变量static的instance未被创建并分配内存空间,当getInstance方法第一次被调用时,初始化instance变量,并分配内存,因此在某些特定条件下会节约了内存。
缺点:在多线程环境中,这种实现方法是错误的,存在一个严重的问题:线程不安全!!,根本不能保证单例的状态。因为该类在第一次调用的时候,如果被两个线程同时调用则可能会创建出两份T对象的实例,不符合单例模式的要求。不过后续再次调用是不会有问题的。
因此我做出如下的修改:
线程安全版:懒汉模式
template<typename T>
class Singleton{
volatile static T* inst; //防止被编译器优化
static std::mutex lock;
public:
static T* GetInstance()
{
if(inst==NULL) //双重判断降低锁冲突的概率,提高性能
{
lock.lock();
if(inst==NULL){
inst=new T(); //用的时候才new一个对象
}
lock.unlock();
}
return inst;
}
};
优化后的代码,在多线程情形下,保证了“懒汉模式”的线程安全。
缺点:众所周知在多线程情形下,synchronized方法通常效率低,显然这不是最佳的实现方案。
代码书写的注意事项:
- 加锁解锁的位置,要在第一次判空后进行加锁,创建单例对象成功后才解锁
- 双重if 判断,可以减少锁的消耗,避免不必要的锁竞争
- volatile 关键字保持内存可见性,防止编译器过度优化
单例模式就总结到这里啦,下篇博客博主准备总结网络相关知识了,不要忘记关注博主哦~