单例模式的描述是: 确保一个类只有一个实例,并提供对该实例的全局访问。
从这段话,我们可以知道,单例模式的最重要特点就是:一个类最多只有一个对象。
对于一个普通类,我么可以生成任意对象,我们为了避免生成太多的类,需要将类的构造函数设为私有。
这样的话,我们为了获取实例,只能借助于类的内部函数,而且必须是static函数(非static函数中均包含一个隐式参数this,由于我们没办法实例化,所以只能通过static函数来获取实例):
1 class Singleton 2 { 3 public: 4 static Singleton *getInstance() 5 { 6 return new Singleton; 7 } 8 private: 9 Singleton() { } 10 };
这样虽然可以生成对象了,但每次都去new,无法保证唯一性,所以我们将对象保存在一个static 指针中:
1 class Singleton 2 { 3 public: 4 static Singleton *getInstance() 5 { 6 if(pInstance_ == NULL) //线程的切换 7 { 8 ::sleep(1); 9 pInstance_ = new Singleton; 10 } 11 12 return pInstance_; 13 } 14 private: 15 Singleton() { } 16 17 static Singleton *pInstance_; 18 }; 19 20 Singleton *Singleton::pInstance_ = NULL;
这样我们每次获取对象时,都会检查该指针是否为空。
这样虽然在单线程中可以通过测试,但在多线程中,由于线程的切换,我们生成的对象将不唯一。
所以,我们需要通过加锁来解决这个问题:
1 class Singleton 2 { 3 public: 4 static Singleton *getInstance() 5 { 6 mutex_.lock(); 7 if(pInstance_ == NULL) //线程的切换 8 pInstance_ = new Singleton; 9 mutex_.unlock(); 10 return pInstance_; 11 } 12 private: 13 Singleton() { } 14 15 static Singleton *pInstance_; 16 static MutexLock mutex_; 17 }; 18 19 Singleton *Singleton::pInstance_ = NULL; 20 MutexLock Singleton::mutex_;
加锁后,虽然解决了这种问题,可是互斥锁会极大的降低系统的并发能力,因为每次调用都要加锁。
测试代码如下:
1 class TestThread : public Thread 2 { 3 public: 4 void run() 5 { 6 const int kCount = 1000 * 1000; 7 for(int ix = 0; ix != kCount; ++ix) 8 { 9 Singleton::getInstance(); 10 } 11 } 12 }; 13 14 int64_t getUTime() 15 { 16 struct timeval tv; 17 ::memset(&tv, 0, sizeof tv); 18 if(gettimeofday(&tv, NULL) == -1) 19 { 20 perror("gettimeofday"); 21 exit(EXIT_FAILURE); 22 } 23 int64_t current = tv.tv_usec; 24 current += tv.tv_sec * 1000 * 1000; 25 return current; 26 } 27 28 int main(int argc, char const *argv[]) 29 { 30 //Singleton s; ERROR 31 32 int64_t startTime = getUTime(); 33 34 const int KSize = 100; 35 TestThread threads[KSize]; 36 for(int ix = 0; ix != KSize; ++ix) 37 { 38 threads[ix].start(); 39 } 40 41 for(int ix = 0; ix != KSize; ++ix) 42 { 43 threads[ix].join(); 44 } 45 46 int64_t endTime = getUTime(); 47 48 int64_t diffTime = endTime - startTime; 49 cout << "cost : " << diffTime / 1000 << " ms" << endl; 50 51 return 0; 52 }
测试结果如下:
cost : 7304 ms
我们可以采用双重锁模式来提高性能。
1 class Singleton 2 { 3 public: 4 static Singleton *getInstance() 5 { 6 if(pInstance_ == NULL) 7 { 8 mutex_.lock(); 9 if(pInstance_ == NULL) //线程的切换 10 pInstance_ = new Singleton; 11 mutex_.unlock(); 12 } 13 14 return pInstance_; 15 } 16 private: 17 Singleton() { } 18 19 static Singleton *pInstance_; 20 static MutexLock mutex_; 21 }; 22 23 Singleton *Singleton::pInstance_ = NULL; 24 MutexLock Singleton::mutex_;
我们在getInstance中采用了双重检查模式,这样做的优点为:
内部采用互斥锁,代码无论如何是可靠的
new出第一个实例后,后面每个线程访问到最外面的if判断就直接返回了,没有加锁的开销
再次测试,结果为:
cost : 486 ms
这样,就简单的完成了对单例模式的一点性能改进。