C++ static对象只会被初始化一次,而且整个应用内只有一个对象,于是经常看到开发人员会将其作为单例对象,一般情况下没有问题,但是在多线程场景下static对象不适合用作单例对象,为什么呢?
首先,让我们先看下static对象作为单例对象的实现代码。
类A通过GetInstance()提供单例对象的访问接口,单例对象使用static a,static关键字保证a只会被初始化一次,在多线程环境下为什么就有问题呢?
这就需要我们对static有个更深入的了解,C++编译器是如何保证static的变量只会被初始化一次。如上所述的代码片段,在C++编译器处理后成为如下的代码片段。
左侧static A a,经过编译器处理后变成右侧代码。编译器自动增加一个static int flag = 0的变量来控制a对象的构造函数只调用一次。flag和a都是static变量,程序运行后就在全局数据区为他们分配空间,并给flag赋值为常量0,由于a需要调用其构造函数初始化,于是延迟到第一次调用GetInstance()才进行初始化。可见,编译器对static变量的初始化是没有加锁的。
接下来,我们分析下多线程环境下,这段代码是如何让程序崩溃的。假设线程T1调用GetInstance()执行对象a的构造函数,构造函数内有一个耗时的操作,构造函数未执行完,CPU切换到线程T2,注意此时对象a处于初始化的过程,但还未完成初始化。线程T2调用GetInstance(),此时flag=1,所以不会调用对象a的构造函数,直接返回对象a的地址,由于对象a没有完全初始化,此时如果T2继续调用对象a的成员方法,大概率会产生崩溃。
所以,单例对象不建议使用static对象,而是通过动态分配,使用双检锁确保多线程安全。
两次判断pInstance是否为空,第一次是为了减少不必要的加锁,提升性能。第二次是为了避免多次实例化。