[剑指offer]面试题2(实现单例模式)

题目:设计一个类,我们只能生成该类的一个实例。
作用:保证为一个类只生成唯一的实例对象。也就是说,在整个程序空间中,该类只存在一个实例对象

关于单例模式,有两种实现方法:饿汉式和懒汉式
饿汉式
优点:加载进程时静态创建单例对象,线程安全
缺点:无论使用与否,总要创建。会造成一定的资源空间浪费

懒汉式
优点:用则创建,不用不创建,什么时候用什么时候创建,空间利用率较高
缺点:在多线程中,首次访问时动态创建单例对象,存在线程不安全的问题

1、不好的解法:只适合单线程
基本思想:只有在m_instance为NULL的时候才创建一个实例,而m_instance是静态属性。同时我们把构造函数定义为私有函数,这样就能确保只有一个实例被创建

#include <iostream>

using namespace std;

class UserManager
{
private:                                     //实现单例常用步骤:
    UserManager()                            //1、构造函数私有化
    {

    }
public:
    static UserManager* GetInstance()        //2、写一个静态的函数去创建对象
    {
        //if (m_instance == NULL)             //这里是懒汉式
        //  m_instance = new UserManager;

        handle_count++;
        return m_instance;
    }

    static void Release()      //释放
    {
        if (handle_count >= 1)
            handle_count--;

        if (m_instance != NULL && handle_count == 0)
        {
            delete m_instance;
            m_instance = NULL;
        }
    }

private:
    static UserManager* m_instance;           //3、创建一个静态的指针保存创建的对象
    static int handle_count;    // 引用计数
};

//UserManager* UserManager::m_instance = NULL;  //懒汉式

UserManager* UserManager::m_instance = new UserManager;     //饿汉式
int UserManager::handle_count = 0;

int main()
{
    UserManager *p1 = UserManager::GetInstance();
    UserManager *p2 = UserManager::GetInstance();

    if (p1 == p2)
    {
        cout << "同一个对象" << endl;
    }
    else
    {
        cout << "不同对象" << endl;
    }
    UserManager::Release();
    UserManager::Release();        //有几个对象就释放几次

    return 0;
}

2、较好的解法:适合多线程
基本思想:如果两个线程同时运行到判断m_instance是否为NULL的if语句,并且m_instance的确没有创建,那么两个线程都会创建一个实例,所以我们需要使用线程互斥锁。当第一个线程上锁时,第二个线程只能等待。当第一个线程发现实例还没有被创建时,它创建一个实例然后解锁,此时第二个线程可以加锁,运行接下来的代码,但是这时候它发现实例已经创建了,所以它直接解锁。这样做有不好的地方,下面在介绍。

#include <iostream>
#include <pthread.h>

using namespace std;

class UserManager
{
private:
    UserManager()                           //1、构造函数私有化
    {
        cout << "构造函数被调用" << endl;
        sleep(5);
    }

public:
    static UserManager* GetInstance()    //2、写一个静态的函数去创建对象
    {
        if (m_instance == NULL)     //注意,为什么if语句判断两次
        {
            pthread_mutex_lock (&mutex_instance);       // 上锁

            if (m_instance == NULL)     
                m_instance = new UserManager;

            pthread_mutex_unlock(&mutex_instance);      //解锁
        }
        handle_count++;
        return m_instance;
    }

    static void Release()
    {
        if (handle_count >= 1)
            handle_count--;
        if (m_instance != NULL && handle_count == 0)
        {
            delete m_instance;
            m_instance = NULL;
        }
    }

    static void *work (void *)  // void *work111(UserManager* tihs, void*)
    {

    }

    void run()
    {
        pthread_t tid;
        pthread_create(&tid, NULL, work, NULL);
    }

private:
    static pthread_mutex_t mutex_instance;
    static UserManager* m_instance;   //3、创建一个静态的指针保存创建的对象

    static int handle_count; // 引用计数
};

pthread_mutex_t UserManager::mutex_instance = PTHREAD_MUTEX_INITIALIZER;//线程互斥锁初始化
UserManager *UserManager::m_instance = NULL;
int UserManager::handle_count = 0;                  //类中静态变量在类外初始化

void *work(void *)
{
    UserManager* pm = UserManager::GetInstance();
}

int main()
{
    pthread_t tid[10];
    for (int i = 0; i < 10; i++)
    {
        pthread_create(&tid[i], NULL, work, NULL);      //创建10个线程

        pthread_detach(tid[i]);                         //线程分离
    }
    pthread_exit(NULL);

    return 0;
}

注意:
1、创建多线程的程序是在Linux下运行的。编译:g++ singlon.cpp -lpthread
运行:./a.out

2、运行结果:打印“构造函数被调用”一次,然后沉睡5秒,程序结束。如果不进行加锁解锁操作,结果变为:打印“构造函数被调用”十次,然后沉睡5秒,程序结束。这样的话就不是单例模式了。所以一定要进行加锁解锁操作。

3、代码中为什么要两次使用同样的if语句进行判断?
我们只是在实例还没有创建之前需要加锁操作,以保证只有一个线程创建出实例。而当实例已经创建之后,我们已经不需要再做加锁操作了。而实际上加锁是一个非常耗时的操作,在没有必要的时候我们应该尽量避免。所以,第一个if语句就是用来屏蔽掉那些不需要加锁解锁操作,提高效率,提高代码的质量。

有兴趣的可以关注一下博主的博客:
这里写图片描述


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值