经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » 设计模式 » 查看文章
剑指offer笔记面试题2----实现Singleton模式
来源:cnblogs  作者:奔跑的亮仔  时间:2019/10/18 8:52:17  对本文有异议

题目:设计一个类,我们只能生成该类的一个实例。

解法一:单线程解法

  1. //缺点:多线程情况下,每个线程可能创建出不同的的Singleton实例
  2. #include <iostream>
  3. using namespace std;
  4. class Singleton
  5. {
  6. public:
  7. static Singleton* getInstance()
  8. {
  9. if(m_pInstance == nullptr)
  10. {
  11. m_pInstance = new Singleton();
  12. }
  13. return m_pInstance;
  14. }
  15. static void destroyInstance()
  16. {
  17. if(m_pInstance != nullptr)
  18. {
  19. delete m_pInstance;
  20. m_pInstance = nullptr;
  21. }
  22. }
  23. private:
  24. Singleton(){}
  25. static Singleton* m_pInstance;
  26. };
  27. Singleton* Singleton::m_pInstance = nullptr;
  28. // 单线程获取多次实例
  29. void Test1(){
  30. // 预期结果:两个实例指针指向的地址相同
  31. Singleton* singletonObj = Singleton::getInstance();
  32. cout << singletonObj << endl;
  33. Singleton* singletonObj2 = Singleton::getInstance();
  34. cout << singletonObj2 << endl;
  35. Singleton::destroyInstance();
  36. }
  37. int main(){
  38. Test1();
  39. return 0;
  40. }

解法二:多线程+加锁

  1. /*解法一是最简单,也是最普遍的实现方式。但是,这种实现方式有很多问题,比如没有考虑多线程的问题,在多线程的情况下,就可能会创建多个Singleton实例,以下是改善的版本。*/
  2. #include <iostream>
  3. #include <mutex>
  4. #include <thread>
  5. #include <vector>
  6. using namespace std;
  7. class Singleton
  8. {
  9. private:
  10. static mutex m_mutex; // 互斥量
  11. Singleton(){}
  12. static Singleton* m_pInstance;
  13. public:
  14. static Singleton* getInstance(){
  15. if(m_pInstance == nullptr){
  16. m_mutex.lock(); // 使用C++11中的多线程库
  17. if(m_pInstance == nullptr){ // 两次判断是否为NULL的双重检查
  18. m_pInstance = new Singleton();
  19. }
  20. m_mutex.unlock();
  21. }
  22. return m_pInstance;
  23. }
  24. static void destroyInstance(){
  25. if(m_pInstance != nullptr){
  26. delete m_pInstance;
  27. m_pInstance = nullptr;
  28. }
  29. }
  30. };
  31. Singleton* Singleton::m_pInstance = nullptr;
  32. mutex Singleton::m_mutex;
  33. void print_singleton_instance(){
  34. Singleton *singletonObj = Singleton::getInstance();
  35. cout << singletonObj << endl;
  36. }
  37. // 多个进程获得单例
  38. void Test1(){
  39. // 预期结果,打印出相同的地址,之间可能缺失换行符,也属正常现象
  40. vector<thread> threads;
  41. for(int i = 0; i < 10; ++i){
  42. threads.push_back(thread(print_singleton_instance));
  43. }
  44. for(auto& thr : threads){
  45. thr.join();
  46. }
  47. }
  48. int main(){
  49. Test1();
  50. Singleton::destroyInstance();
  51. return 0;
  52. }
  53. /*此方法中进行了两次m_pInstance == nullptr的判断,使用了所谓的“双检锁”机制。因为进行一次加锁和解锁是需要付出对应的代价的,而进行两次判断,就可以避免多次加锁与解锁操作,只在m_pInstance不为nullptr时才需要加锁,同时也保证了线程安全。但是,如果进行大数据的操作,加锁操作将成为一个性能的瓶颈,为此,一种新的单例模式的实现也就出现了。*/

解法三:const static型实例

  1. #include <iostream>
  2. #include <thread>
  3. #include <vector>
  4. using namespace std;
  5. class Singleton
  6. {
  7. private:
  8. Singleton(){}
  9. static const Singleton* m_pInstance;
  10. public:
  11. static Singleton* getInstance(){
  12. return const_cast<Singleton*>(m_pInstance); // 去掉“const”特性
  13. // 注意!若该函数的返回值改为const static型,则此处不必进行const_cast静态转换
  14. // 所以该函数可以改为:
  15. /*
  16. const static Singleton* getInstance(){
  17. return m_pInstance;
  18. }
  19. */
  20. }
  21. static void destroyInstance(){
  22. if(m_pInstance != NULL){
  23. delete m_pInstance;
  24. m_pInstance = NULL;
  25. }
  26. }
  27. };
  28. const Singleton* Singleton::m_pInstance = new Singleton(); // 利用const只能定义一次,不能再次修改的特性,static继续保持类内只有一个实例
  29. void print_singleton_instance(){
  30. Singleton *singletonObj = Singleton::getInstance();
  31. cout << singletonObj << endl;
  32. }
  33. // 多个进程获得单例
  34. void Test1(){
  35. // 预期结果,打印出相同的地址,之间可能缺失换行符,也属正常现象
  36. vector<thread> threads;
  37. for(int i = 0; i < 10; ++i){
  38. threads.push_back(thread(print_singleton_instance));
  39. }
  40. for(auto& thr : threads){
  41. thr.join();
  42. }
  43. }
  44. int main(){
  45. Test1();
  46. Singleton::destroyInstance();
  47. return 0;
  48. }
  49. /*因为静态初始化在程序开始时,也就是进入主函数之前,由主线程以单线程方式完成了初始化,所以静态初始化实例保证了线程安全性。在性能要求比较高时,就可以使用这种方式,从而避免频繁的加锁和解锁造成的资源浪费。由于上述三种实现,都要考虑到实例的销毁,关于实例的销毁,待会在分析。*

解法四:在get函数中创建并返回static临时实例的引用

  1. //PS:该方法不能认为控制单例实例的销毁
  2. #include <iostream>
  3. #include <thread>
  4. #include <vector>
  5. using namespace std;
  6. class Singleton
  7. {
  8. private:
  9. Singleton(){}
  10. public:
  11. static Singleton* getInstance(){
  12. static Singleton m_pInstance; // 注意,声明在该函数内
  13. return &m_pInstance;
  14. }
  15. };
  16. void print_singleton_instance(){
  17. Singleton *singletonObj = Singleton::getInstance();
  18. cout << singletonObj << endl;
  19. }
  20. // 多个进程获得单例
  21. void Test1(){
  22. // 预期结果,打印出相同的地址,之间可能缺失换行符,也属正常现象
  23. vector<thread> threads;
  24. for(int i = 0; i < 10; ++i){
  25. threads.push_back(thread(print_singleton_instance));
  26. }
  27. for(auto& thr : threads){
  28. thr.join();
  29. }
  30. }
  31. // 单个进程获得多次实例
  32. void Test2(){
  33. // 预期结果,打印出相同的地址,之间换行符分隔
  34. print_singleton_instance();
  35. print_singleton_instance();
  36. }
  37. int main(){
  38. cout << "Test1 begins: " << endl;
  39. Test1();
  40. cout << "Test2 begins: " << endl;
  41. Test2();
  42. return 0;
  43. }

解法五:最终方案,最简&显式控制实例销毁

  1. /*在实际项目中,特别是客户端开发,其实是不在乎这个实例的销毁的。因为,全局就这么一个变量,全局都要用,它的生命周期伴随着软件的生命周期,软件结束了,他就自然而然结束了,因为一个程序关闭之后,它会释放它占用的内存资源的,所以,也就没有所谓的内存泄漏了。
  2. 但是,有以下情况,是必须要进行实例销毁的:
  3. 在类中,有一些文件锁了,文件句柄,数据库连接等等,这些随着程序的关闭而不会立即关闭的资源,必须要在程序关闭前,进行手动释放。*/
  4. #include <iostream>
  5. #include <thread>
  6. #include <vector>
  7. using namespace std;
  8. class Singleton
  9. {
  10. private:
  11. Singleton(){}
  12. static Singleton* m_pInstance;
  13. // **重点在这**
  14. class GC // 类似Java的垃圾回收器
  15. {
  16. public:
  17. ~GC(){
  18. // 可以在这里释放所有想要释放的资源,比如数据库连接,文件句柄……等等。
  19. if(m_pInstance != NULL){
  20. cout << "GC: will delete resource !" << endl;
  21. delete m_pInstance;
  22. m_pInstance = NULL;
  23. }
  24. };
  25. };
  26. // 内部类的实例
  27. static GC gc;
  28. public:
  29. static Singleton* getInstance(){
  30. return m_pInstance;
  31. }
  32. };
  33. Singleton* Singleton::m_pInstance = new Singleton();
  34. Singleton::GC Singleton::gc;
  35. void print_instance(){
  36. Singleton* obj1 = Singleton::getInstance();
  37. cout << obj1 << endl;
  38. }
  39. // 多线程获取单例
  40. void Test1(){
  41. // 预期输出:相同的地址,中间可能缺失换行符,属于正常现象
  42. vector<thread> threads;
  43. for(int i = 0; i < 10; ++i){
  44. threads.push_back(thread(print_instance));
  45. }
  46. for(auto& thr : threads){
  47. thr.join();
  48. }
  49. }
  50. // 单线程获取单例
  51. void Test2(){
  52. // 预期输出:相同的地址,换行符分隔
  53. print_instance();
  54. print_instance();
  55. print_instance();
  56. print_instance();
  57. print_instance();
  58. }
  59. int main()
  60. {
  61. cout << "Test1 begins: " << endl;
  62. cout << "预期输出:相同的地址,中间可以缺失换行(每次运行结果的排列格式通常不一样)。" << endl;
  63. Test1();
  64. cout << "Test2 begins: " << endl;
  65. cout << "预期输出:相同的地址,每行一个。" << endl;
  66. Test2();
  67. return 0;
  68. }
  69. /*在程序运行结束时,系统会调用Singleton的静态成员GC的析构函数,该析构函数会进行资源的释放,而这种资源的释放方式是在程序员“不知道”的情况下进行的,而程序员不用特别的去关心,使用单例模式的代码时,不必关心资源的释放。
  70. 那么这种实现方式的原理是什么呢?由于程序在结束的时候,系统会自动析构所有的全局变量,系统也会析构所有类的静态成员变量,因为静态变量和全局变量在内存中,都是存储在静态存储区的,所有静态存储区的变量都会被释放。由于此处是用了一个内部GC类,而该类的作用就是用来释放资源。这种技巧在C++中是广泛存在的,参见《C++中的RAII机制》。*/

原文链接:http://www.cnblogs.com/tangliang39/p/11694673.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号