我们知道,设计模式有着深邃的思想。现在来看看单例模式。

它的核心思想在于:只能有一个实例(多线程也是如此)

看看如何实现👇

 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

Java选手,参考这里