#include <thread>
#include <iostream>
#include <mutex>
// 最原始的单例模式的写法,不是线程安全的,并且会内存泄漏。
// 线程不安全的原因:假设有两个线程都执行getInstance函数。当线程1调用singleton = new Singleton1()
// 语句时候,操作系统系统突然切换到线程2,线程2判断if (singleton == nullptr)生效,线程2执行
// singleton = new Singleton1();当线程2执行完后,singleton已经生成。然后切换到线程1,线程1继续执行
// singleton = new Singleton1(),singleton会再次生成。这不符合单例设计的原则。
// 内存泄漏的原因:析构函数没法调用,所以无法通过析构函数调用delete,删除singleton内存
class Singleton1
{
public:
~Singleton1() {std::cout << "Singleton1析构函数调用" << std::endl;} // 析构函数其实不会调用,所以new出来的静态成员变量会内存泄漏。
static Singleton1* getInstance()
{
if (singleton == nullptr)
{
singleton = new Singleton1();
}
return singleton;
}
void func()
{
printf("调用func函数\n");
}
private:
// static函数只能调用静态成员变量或者静态函数,所以下面这个静态成员变量必须为static
static Singleton1* singleton;
Singleton1(){}
};
// 静态非const整形成员变量必须在类外定义
Singleton1* Singleton1::singleton = nullptr;
// 再写个单例模式,采用类中类解决内存泄露的问题。其实在main函数中也可以手动delete,只不过不是很优雅。
class Singleton2
{
private:
Singleton2(){}
static Singleton2* singleton;
public:
~Singleton2()
{
std::cout << "Singleton2析构函数调用" << std::endl;
} // 析构函数其实不会调用,所以new出来的静态成员变量会内存泄漏。
static Singleton2* getInstance()
{
if (singleton == nullptr)
{
singleton = new Singleton2();
static PtrCycle ptr; // C++能确保静态变量只能被初始化一次,不会因为调用getInstance,多次创建静态对象。
}
return singleton;
}
void func()
{
printf("调用func函数\n");
}
private:
class PtrCycle
{
public:
~PtrCycle()
{
if (singleton)
{
delete singleton; //这里会调用析构函数
singleton = nullptr;
std::cout << "释放内存" << std::endl;
}
}
};
};
Singleton2* Singleton2::singleton = nullptr; //必须要在类外初始化
// 上面的写法还是线程不安全的。为了解决线程安全,引申出下面的饿汉式和懒汉式写法。
// 饿汉式:一开始就初始化单例对象
// 饿汉式写法一:把对象用new放在堆上。
class Singleton3
{
private:
static Singleton3* singleton;
Singleton3(){}
public:
void func()
{
printf("调用func函数\n");
}
static Singleton3* getInstance()
{
static PtrCycle ptr;
return singleton;
}
~Singleton3(){std::cout << "Singleton3析构函数" << std::endl;}
private:
class PtrCycle
{
public:
~PtrCycle()
{
if (singleton)
{
delete singleton; // 这里会调用析构函数
singleton = nullptr;
std::cout << "释放内存" << std::endl;
}
}
};
};
Singleton3* Singleton3::singleton = new Singleton3(); //静态对象类外初始化,其实这个写法不好
// 上面new出来的这个指针,如果getInstace函数从没被调用过,那么因为new Singleton3()
// 得到的内存从没被释放,会发生内存泄漏。
// 饿汉式写法二:把对象放在静态区,不使用new。
// 这种写法不需要写类中类去释放内存,或者在main函数中手动删除内存
class Singleton4
{
private:
static Singleton4 singleton;
Singleton4(){}
public:
void func()
{
printf("调用func函数\n");
}
~Singleton4(){std::cout << "Singleton4析构函数" << std::endl;}
static Singleton4* getInstance()
{
return &singleton;
}
};
Singleton4 Singleton4::singleton; // 静态对象类外初始化
// 饿汉式的总结
// 由于在定义静态变量的时候实例化单例类,因此在类加载的时候就已经创建了单例对象,可确保单例对象的唯一性。线程是安全的。
// 缺点:无论系统运行时是否需要使用该单例对象,都会在类加载时创建对象,资源利用效率不高。
// 懒汉式:需要时候再实例化单例对象。
// 懒汉式1:直接加个锁。
// 这样的代码其实有个很严重的问题,就是代码中可能需要频繁调用getInstance这个函数
// 因为只有借助getInstace这个函数才能获取到单例类对象,然后才能调用单例类的其他成员
// 函数。为了解决一个初始化该类对象的互斥问题,居然在getInstace里面加了互斥量。导致
// 所有时刻,调用getInstance这个函数,都会因为锁互斥一下,严重影响性能。因为除了初始化时刻,其他
// 时候完全不需要互斥。一旦初始化完成,if (singleton == nullptr)永远不会成立,所以singleton = new Singleton()
// 永远不会再次执行。
class Singleton5
{
private:
static Singleton5* singleton;
static std::mutex my_mutex; //这里用的静态成员变量,保证所有用到这个类的,用的是同一个互斥量。当然定义一个全局互斥量也可以。
Singleton5(){}
class PtrCycle
{
public:
~PtrCycle()
{
if (singleton)
{
delete singleton; //这里会调用析构函数
singleton = nullptr;
std::cout << "释放内存" << std::endl;
}
}
};
public:
~Singleton5(){std::cout << "Singleton5析构函数执行" << std::endl;}
static Singleton5* getInstance()
{
std::lock_guard<std::mutex> my_guard(my_mutex);
if (singleton == nullptr)
{
singleton = new Singleton5();
static PtrCycle ptr;
}
return singleton;
}
void func()
{
printf("调用func函数\n");
}
};
std::mutex Singleton5::my_mutex;
Singleton5* Singleton5::singleton = nullptr;
// 懒汉式2:双重锁定
// 双重锁定的写法,保证线程1在if (singleton == nullptr)成立之后,
// singleton = new Singleton6();运行之前,一定不会发生上下文的切换。
// 因此会创建完成单例类对象。然后互斥量解锁之后,哪怕发生上下文切换,换到了另一个
// 线程,此时if (singleton == nullptr)一定不会成立,因此不会再调用第二次 singleton = new Singleton6()。
// 初始化时候,需要用到这个互斥量加锁,其他时候并不会用到这个互斥量。因为一旦初始化完成之后
// if (singleton == nullptr)一定不会成立,因此不会因为调用一次getInstance就创建一次互斥量。
// 因此大大提升了代码的运行效率。
class Singleton6
{
private:
static Singleton6* singleton;
static std::mutex my_mutex;
Singleton6(){}
class PtrCycle
{
public:
~PtrCycle()
{
if (singleton)
{
delete singleton; //这里会调用析构函数
singleton = nullptr;
std::cout << "释放内存" << std::endl;
}
}
};
public:
~Singleton6(){std::cout << "Singleton6析构函数执行" << std::endl;}
static Singleton6* getInstance()
{
if (singleton == nullptr)
{
std::lock_guard<std::mutex> my_guard(my_mutex);
if (singleton == nullptr)
{
singleton = new Singleton6();
static PtrCycle ptr;
}
}
return singleton;
}
void func()
{
printf("调用func函数\n");
}
};
std::mutex Singleton6::my_mutex;
Singleton6* Singleton6::singleton = nullptr;
// C++11之后,静态局部对象是实现多线程安全的单例类最佳写法。
// C++11之后,多个线程同时初始化一个同一局部静态对象,可以保证只初始化一次。
// 在实现单例的过程中要注意如下问题:
// 1. 构造函数应该声明为非公有,从而禁止外界创建实例。
// 2. 拷贝操作和移动操作也应该禁止。
// 3. 只能通过 Singleton 的公有特定类操作访问它的唯一实例(C++中的一个公有静态成员函数)
class Singleton7
{
public:
~Singleton7(){std::cout << "Singleton7析构函数执行" << std::endl;}
static Singleton7* getInstance()
{
static Singleton7 singleton_tmp;
return &singleton_tmp;
}
void func()
{
printf("调用func函数\n");
}
private:
Singleton7(){}
// 拷贝构造函数
Singleton7(const Singleton7& singleton) = delete;
// 拷贝赋值函数
Singleton7& operator = (const Singleton7& singleton) = delete;
// 移动构造函数
Singleton7(Singleton7&& singleton) = delete;
// 移动赋值构造函数
Singleton7& operator = (Singleton7&& singleton) = delete;
};
void my_thread()
{
printf("thread run\n");
Singleton3* s = Singleton3::getInstance();
printf("address is: %p \n", s);
s->func();
}
int main()
{
std::thread my_thread1(my_thread);
std::thread my_thread2(my_thread);
my_thread1.join();
my_thread2.join();
return 0;
}
不过有一点需要说明的是:将单例类放在主线程中,在其他子线程创建并运行之前,将单例类初始化完成是强烈推荐的。这样就不存在多个子线程对这个单例类对象访问的冲突问题,因为一旦初始化完成,再次调用getInstance的操作全是读操作,是线程安全的。
如果你需要在自己创建的子线程中创建单例类对象,为了保证多线程安全,可以参考我的代码写法。