设计模式:
设计模式(Design Pattern)**是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。**为什么会产生设计模式这样的东西呢?就像人类历史发展会产生兵法。最开始部落之间打仗时都是人拼人的对
砍。后来春秋战国时期,七国之间经常打仗,就发现打仗也是有套路的,后来孙子就总结出了《孙子兵法》。孙子兵法也是类似。
使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编
写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。
单例模式:
一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。比如在某个服务器程序中,该服务器的配置信息存放在一个文件
中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。
单例模式有两种实现模式:
理解:
就是说不管你将来用不用,程序启动时就创建一个唯一的实例对象。
单例(唯一的实例对象)
实现:
1.与实现唯一抽象类原理相同,通过私有化构造函数和析构函数实现单例(唯一对象)
2.通过static
关键字,实现访问其私有成员并创建对象
//单例:
//饿汉 --> 一上来就初始化,main函数开始之间就实例化对象
class Singleton
{
public:
static Singleton& GetInstance()
{
return _sInst;
}
private:
//构造函数私有化
Singleton()
{}
//拷贝构造函数私有化
Singleton(const Singleton&) = delete;
static Singleton _sInst;//声明
};
Singleton Singleton::_sInst;
int main()
{
cout << &Singleton::GetInstance() << endl;
cout << &Singleton::GetInstance() << endl;
cout << &Singleton::GetInstance() << endl;
cout << &Singleton::GetInstance() << endl;
system("pause");
return 0;
}
验证:
int main()
{
/*cout << &Singleton::GetInstance() << endl;
cout << &Singleton::GetInstance() << endl;
cout << &Singleton::GetInstance() << endl;
cout << &Singleton::GetInstance() << endl;*/
// 多线程来调用Singleton::GetInstance(),有没有线程安全的问题?
//
vector<thread> vthreads;
for (size_t i = 0; i < 4; ++i)
{
vthreads.push_back(thread([]()
{
this_thread::sleep_for(std::chrono::seconds(1));
for (size_t i = 0; i < 100; ++i)
{
cout << &Singleton::GetInstance() << endl;
}
}));
}
for (auto& e : vthreads)
{
e.join();
}
system("pause");
return 0;
}
可以看到是没有问提的
饿汉模式下是没有线程安全问题的,因为它在mian()函数调用之间,已经实例化了对象,main()调用后多线程才会进入,所以不存在线程安全问题。
优点:实现简单,不存在线程安全的问题
缺点:在main函数之前初始化实例,如果程序中单例较多,程序启动慢,性能过低。其次如果两个单例类有依赖关系,无法保证创建初始化实例的顺序。
理解:
使用时才加载资源,第一次使用实例对象时,创建对象。进程启动无负载。多个单例实例启动顺序自由控制。
概念:如果单例对象构造十分耗时或者占用很多资源,比如加载插件啊, 初始化网络连接啊,读取文件啊等
等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化(饿汉模式),就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式(延迟加载)更好。
实现:
1.同理,私有化构造函数和拷贝构造函数,实现单例
2.static
关键字修饰函数变量,访问私有成员
与饿汉模式不同,在于懒汉模式在类中封装的是对象指针,所以static
初始化的是指针类型。
class Singleton
{
public:
static Singleton& GetInstance()
{
if (_sInst == nullptr)
{
_sInst = new Singleton;
}
return *_sInst;
}
private:
//构造函数私有化
Singleton()
{}
//拷贝构造函数私有化
Singleton(const Singleton&) = delete;
static Singleton* _sInst;//声明
};
Singleton* Singleton::_sInst = nullptr;
int main()
{
cout << &Singleton::GetInstance() << endl;
cout << &Singleton::GetInstance() << endl;
cout << &Singleton::GetInstance() << endl;
cout << &Singleton::GetInstance() << endl;
system("pause");
return 0;
}
调用GetInstance()
接口申请空间实例化指针所指向空间,后面进程再进来时无需再次申请空间,提高性能
代码测试:
int main()
{
/*cout << &Singleton::GetInstance() << endl;
cout << &Singleton::GetInstance() << endl;
cout << &Singleton::GetInstance() << endl;
cout << &Singleton::GetInstance() << endl;*/
// 多线程来调用Singleton::GetInstance(),有没有线程安全的问题?
//
vector<thread> vthreads;
for (size_t i = 0; i < 100; ++i)
{
vthreads.push_back(thread([]()
{
this_thread::sleep_for(std::chrono::seconds(1));
for (size_t i = 0; i < 10; ++i)
{
cout << &Singleton::GetInstance() << endl;
}
}));
}
for (auto& e : vthreads)
{
e.join();
}
system("pause");
return 0;
}
饿汉模式在多线程下不是线程安全的,如上图但这是一个随机性bug,并不出每次都会出现
问题原因:
假设有多线程场景下,第一个线程进入时指针为空申请空间,但这个线程如果还没来得及退出,第二个线程进来判断时,指针仍为空,再次申请空间,导致无法实现单例模式下的线程安全。
解决方法:
加锁,若指针为空,第一个线程进入后,加锁操作,其他线程均被阻塞,无法进入,当第一个线程资源空间申请后解决,其他线程再进入,就可以解决。
class Singleton
{
public:
static Singleton& GetInstance()
{
_stx.lock();
if (_sInst == nullptr)
{
_sInst = new Singleton;
}
_stx.unlock();
return *_sInst;
}
private:
//构造函数私有化
Singleton()
{}
//拷贝构造函数私有化
Singleton(const Singleton&) = delete;
static Singleton* _sInst;//声明
static mutex _stx;
};
Singleton* Singleton::_sInst = nullptr;
mutex Singleton::_stx;
可多次运行,看是否解决
此时已经解决懒汉模式的线程安全问题。
优化:
每次线程进入时都要进行加锁解锁操作,由于锁调度也会耗费资源影响性能。而且也只有在指针为空时存在线程安全的问题,所以我们可以再加一层判断,若指针为空则加锁,若不为空则不用加锁
懒汉模式的完整优化代码
class Singleton
{
public:
static Singleton& GetInstance()
{
//双重检查提高效率
if (_sInst == nullptr)
{
_stx.lock();
if (_sInst == nullptr)
{
_sInst = new Singleton;
}
_stx.unlock();
}
return *_sInst;
}
private:
//构造函数私有化
Singleton()
{}
//拷贝构造函数私有化
Singleton(const Singleton&) = delete;
static Singleton* _sInst;//声明
static mutex _stx;
};
Singleton* Singleton::_sInst = nullptr;
mutex Singleton::_stx;
int main()
{
/*cout << &Singleton::GetInstance() << endl;
cout << &Singleton::GetInstance() << endl;
cout << &Singleton::GetInstance() << endl;
cout << &Singleton::GetInstance() << endl;*/
// 多线程来调用Singleton::GetInstance(),有没有线程安全的问题?
//
vector<thread> vthreads;
for (size_t i = 0; i < 100; ++i)
{
vthreads.push_back(thread([]()
{
this_thread::sleep_for(std::chrono::seconds(1));
for (size_t i = 0; i < 10; ++i)
{
cout << &Singleton::GetInstance() << endl;
}
}));
}
for (auto& e : vthreads)
{
e.join();
}
system("pause");
return 0;
}
懒汉模式更为常用
优点:是在第一次调用初始化创建实例,不影响程序的启动。其次他调用时创建初始化,顺序可以控制
缺点:实现相对复杂