我们知道,设计模式有着深邃的思想。现在来看看单例模式。
它的核心思想在于:只能有一个实例(多线程也是如此)
看看如何实现👇
1 class Singleton 2 { 3 private: 4 Singleton(); 5 static Singleton* instance; 6 public: 7 ~Singleton(); 8 static Singleton* getIntance(); 9 static void destroy(); 10 };
1 Singleton::Singleton() 2 { 3 cout << "constructor call..." << endl; 4 } 5 Singleton::~Singleton() 6 { 7 cout << "default destroy call..." << endl; 8 } 9 10 Singleton* Singleton::instance = new Singleton(); 11 Singleton* Singleton::getIntance() 12 { 13 if (instance == nullptr) 14 instance = new Singleton(); 15 return instance; 16 } 17 18 void Singleton::destroy() 19 { 20 delete instance; 21 instance = nullptr; 22 cout << "destroy call..." << endl; 23 }
具体的方法是:
1. 将构造函数设为私有private static(包括拷贝构造和赋值符=)
2. 定义私有的private instance实例
3. 提供共有的public getInstance(),来访问和返回instance
4. 自定义销毁函数,否则以上的用法会内存泄露。
test
1 int main() 2 { 3 //单线程单例 4 Singleton* single1 = Singleton::getIntance(); 5 Singleton* single2 = Singleton::getIntance(); 6 if (single1 == single2) 7 cout << "地址相同" << endl; 8 9 single1->destroy();//不定义销毁函数,会产生内存泄漏。 10 11 return 0; 12 }
(single1 和 single2 指向的是相同的实例instance,所以只会构造一次,地址相同;而default destroy 是在destroy手动析构,deleteinstance 时调用的,至于为什么default在先,看destroy函数就知道了,输出的先后而已,其实是先进入destroy函数的。)
上面👆的写法在多线程下并不安全,当两个线程同时运行到instance==nullptr时, 就会产生两个实例,这样就破坏了单例的原则---只有一个实例!
安全的单例模式
【1】首先我们会想到互斥锁,让关键代码成为临界区。👇
1 Singleton* Singleton::getIntance() 2 { 3 mutex.lock(); 4 if (instance == nullptr) 5 instance = new Singleton(); 6 mutex.unlock(); 7 return instance; 8 }
这样做可以是的线程同步,但效率太低,我们试想,所有的线程都要等待解锁,其实只要第一个进入后对象产生, 其他的就可以直接返回instance了,不必要同步的等待开锁,浪费了时间。
怎么提高效率呢?👇
【2】double check双重检查
1 Singleton* Singleton::getIntance() 2 { 3 if (instance == nullptr)//check1 4 { 5 imutex.lock(); 6 if (instance == nullptr)//check2 7 instance = new Singleton(); 8 imutex.unlock(); 9 } 10 11 return instance; 12 }
从【1】我们可以看出,只要第一个线程进入临界区,其他的不需要进入,只要拿到实例返回就可以了,所以我们进行double check,第一个线程进来时为nullptr,进入实例化了instance;其他的线程运行到
if (instance == nullptr)//check1
时,instance != nullptr, 直接就返回。这样一来,大大提高了并发效率。
甚至我们可以从Tomcat Servlet 编译jsp生成的Java文件中见到它的身影!
图看不清,看源码吧!
1 public javax.el.ExpressionFactory _jsp_getExpressionFactory() { 2 if (_el_expressionfactory == null) { 3 synchronized (this) { 4 if (_el_expressionfactory == null) { 5 _el_expressionfactory = _jspxFactory.getJspApplicationContext(getServletConfig().getServletContext()).getExpressionFactory(); 6 } 7 } 8 } 9 return _el_expressionfactory; 10 } 11 12 public org.apache.tomcat.InstanceManager _jsp_getInstanceManager() { 13 if (_jsp_instancemanager == null) { 14 synchronized (this) { 15 if (_jsp_instancemanager == null) { 16 _jsp_instancemanager = org.apache.jasper.runtime.InstanceManagerFactory.getInstanceManager(getServletConfig()); 17 } 18 } 19 } 20 return _jsp_instancemanager; 21 }
总结:
1. 单例要加锁
2. 最好double check