设计模式
产生:
是大佬们针对一些经典的常见的场景, 给定了一些对应的解决方案, 这个就是 设计模式
单例模式
概念:一个对象只可以(需要)被实例化出一次
单例模式优点:
- 在单例模式中,活动的单例只有一个实例,对单例类的所有实例化得到的都是相同的一个实例。这样就防 止其它对象对自己的实例化,确保所有的对象都访问一个实例
- 单例模式具有一定的伸缩性,类自己来控制实例化进程,类就在改变实例化进程上有相应的伸缩性。
- 提供了对唯一实例的受控访问。
- 由于在系统内存中只存在一个对象,因此可以 节约系统资源,当 需要频繁创建和销毁的对象时单例模式无疑可以提高系统的性能。
- 允许可变数目的实例。
- 避免对共享资源的多重占用。
单例模式缺点:
- 不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不 能保存彼此的状态。
- 由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。
- 单例类的职责过重,在一定程度上违背了“单一职责原则”。
- 滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。
懒汉模式
对象使用的时候再实例化,程序初始化加载快运行时流畅度较差,需要注意线程安全问题
template <typename T>
class Singleton{
volatile static T* inst;
static std::mutex lock;
public:
static T* GetInstance(){
if (inst == nullptr){
lock.lock();
if(inst == nullptr){
inst = new T;
}
lock.unlock();
}
return inst;
}
};
为啥要加锁:
instance=new MyManger3();
这个语句不是一个原子操作会造成线程不安全,编译后会多条字节码指令:
- 为new出来的对象开辟内存空间
- 初始化,执行构造器方法的逻辑代码片段
- 完成instance引用的赋值操作,将其指向刚刚开辟的内存地址
线程不安全的场景
假设两个线程t1,t2先后达到 inst = new T;此时t1执行new操作由于操作不是原子性的,t1可能由于时间片不够
或者其他原因陷入休眠由于没有锁线程t2进入该操作并执行完了new操作,此时t1被唤醒继续创建了一个新的对象
这样就不符合单例模式的规则了
double check的机制
目的是为了提高效率;只有第一个创建对象的线程才需要检验,之后的不需要所以先筛选一下避免过度竞争
饿汉模式
程序初始化时已经实例化资源全部加载,因此运行速度快流畅,加载时间很长
template <typename T>
class singleton{
static T data;
public:
static T* GetInstance(){
return &data;
}
};
成员变量和GetInstance()是static的原因
static 确保只在类加载的时候才初始化一次。单例模式是运行的当前虚拟机中有且只有一个需要的对象,不存在重复(通过private构造方法控制不让外界访问)。
static 是给类静态成员变量使用的,属于类的属性,一般是一些常量之类的东西,从加载上来说对于类和对象之间,在类加载到内存时候静态成员变量就存在了,而对象还不存在。
另外 静态方法只能调用静态方法和静态变量,如果全部搞成静态方法那么意味着其他成员变量都要是静态的,很不方便。
static占用内存,如果对象过多的话会占用非常多,其次安全性很不好,多会被内存溢出攻击影响,整理内存软件对于这样的程序性能影响也很大