• 《剑指offer》面试题2:实现Singleton 模式


    面试题2:实现Singleton 模式

    题目:设计一个类,我们只能生成该类的一个实例。

      只能生成一个实例的类是实现了Singleton (单例)模式的类型。由于设计模式在面向对象程序设计中起着举足轻重的作用,在面试过程中很多公司都喜欢问一些与设计模式相关的问题。在常用的模式中, Singleton是唯一一个能够用短短几十行代码完整实现的模式。因此,写一个Singleton的类型是一个很常见的面试题。
    单例模式有三种经典的设计方案:

    1. 延时加载,也称为懒汉模式 ,需要的时候才创建对象,也就是下面方法一用到的
    2. 双重锁模式,需要的时候才创建对象,线程安全,懒汉模式升级版,也就是下面方法二用到的
    3. 贪婪加载,也称为饿汉模式 , 程序执行前就创建好对象,也就是下面方法三用到的
    4. 测试,简单测试。



      解法一:只适用于单线程环境

      由于要求只能生成一个实例,因此我们必须把构造函数设为私有函数以禁止他人创建实例。我们可以定义一个静态的实例,在需要的时候创建该实例。下面定义类型Singletonl就是基于这个思路的实现;

    class SingleTon
    {
    public:
    	static SingleTon* getInstance()
    	{
    		if (instance== NULL)
    		{
    			instance= new SingleTon();	//堆区申请对象
    		}
    
    		return instance;
    	}
    private:
    	SingleTon(){}			//隐藏构造函数接口
    	SingleTon(const SingleTon&);//拷贝构造函数
    	static SingleTon* instance;	//静态指针可以不依赖对象调用,保存唯一实例地址
    };
    SingleTon* SingleTon::instance= NULL;
    

      上述代码在Singleton的静态属性Instance中,只有在instance为null的时候才创建一个实例以避免重复创建。同时我们把构造函数定义为私有函数,这样就能确保只创建一个实例。

      注意:只适用于单线程环境,在多线程中,如果一个现成在刚完成if (instance== NULL)这段代码时,还没有来的及创建对象时,另一个线程也执行到这个阶段,如此一来,程序就会出现问题,也不满足我们单例模式的目的。

      加同步锁:if (instance== NULL)这段语句前后加锁,实现每次只能有一个线程访问

      通常在解决线程安全类问题常用的方法就是加锁,限制对临界区的访问。下面这段代码虽然解决线程安全问题,但程序的效率却低了不少。

    class SingleTon
    {
    public:
    	static SingleTon* getInstance()
    	{
    		lock();	//加锁 	注:并未具体实现此函数,这里仅仅做演示
    		if (instance== NULL)
    		{
    			instance= new SingleTon();	//堆区申请对象
    		}
    		unlock(); //解锁
    		
    		return instance;
    	}
    private:
    	SingleTon(){}			//隐藏构造函数接口
    	SingleTon(const SingleTon&);//拷贝构造函数
    	static SingleTon* instance;	//静态指针可以不依赖对象调用,保存唯一实例地址
    };
    SingleTon* SingleTon::instance= NULL;
    

      加锁解锁机制: 线程A在执行到lock()时,如果发现无法加锁,即有别的线程B正在使用(加锁)此临界资源,当A发现可以进行加锁操作时,即B已经执行完成并进行了解锁操作,此时A再加锁(防止别的线程执行),当A线程执行完遇到代码unlock()时进行解锁操作。
      但是,还有一个问题,当唯一的一个实例已经被创建出来后,后面的线程执行到此段代码时,无需再构建实例,但lock() unlock()会被反复执行,程序进行了许多不必要的开销,降低了效率。


    难道真的鱼(线程安全)与熊掌(效率)不可兼得乎?


    方法二:双重锁模式

      双重锁模式:在加锁前再判断一次,是否需要执行以下内容。虽然叫双重锁,可并不是真正的加了两重锁哦,只是利用了一点技巧而已,可见,有时候只要多想一点点总会有办法的嘛。

    class SingleTon
    {
    public:
    	static SingleTon* getInstance()
    	{
    		if (instance== NULL)		//对象已经生成,无需加锁、解锁操作
    		{
    			lock();
    			if (instance== NULL)
    			{
    				instance= new SingleTon();
    			}
    			unlock();
    		}
    		return instance;
    	}
    private:
    	SingleTon(){}
    	SingleTon(const SingleTon&);
    	static SingleTon* instance;
    };
    


    方法三:提前创建实例

    由于静态变量在代码段存放,在程序刚被加载时就已经存在,因此可以利用这一特点重新设计程序,直接构造实例并让静态的指针instance接收。

    class SingleTon
    {
    public:
    	static SingleTon* getInstance()
    	{
    		return instance;
    	}
    private:
    	SingleTon(){}
    	SingleTon(const SingleTon&);
    	static SingleTon* instance;
    };
    SingleTon* SingleTon::instance= new SingleTon();
    


    测试:

    Person类,属性有mname mage msex

    class Person
    {
    public:
    	static Person* getInstance(char* name, int age, bool sex)
    	{
    		if (pm == NULL)
    		{
    			pm = new Person(name, age, sex);
    		}
    		return pm;
    	}
    	~Person()
    	{
    		delete[] mname;
    		mname = NULL;
    	}
    private:
    	Person(char* name, int age, bool sex)
    	{
    		mname = new char[strlen(name) + 1]();
    		strcpy_s(mname, strlen(name) + 1, name);
    		mage = age;
    		msex = sex;
    	}
    	char* mname;
    	int mage;
    	bool msex;
    	static Person* pm;
    };
    Person* Person::pm = NULL;
    
    int main()
    {
    	char name[] = "zhangsan";
    	Person* p1 = Person::getInstance(name, 25, true);
    	Person* p2 = Person::getInstance(name, 25, true);
    	Person* p3 = Person::getInstance(name, 25, true);
    	Person* p4 = Person::getInstance(name, 25, true);
    	return 0;
    }
    
    

    运行结果:
    在这里插入图片描述
    可以看到运行结果中,不论getInstance方法调用多少次,始终只有一个实例产生。

  • 相关阅读:
    android的Fragment
    c#接口深入一步探究其作用,适合新人了解
    浅谈c#接口的问题,适合新手来了解
    再谈“我是怎么招聘程序员的”(下)转
    再谈“我是怎么招聘程序员的”(上)转
    关于如何写出优秀的代码(转)
    winserver服务器安全部署详细文档
    VS高效的调试技巧
    常用正则表达式(转自月光博客)
    JavaScript数组方法的兼容性写法 汇总:indexOf()、forEach()、map()、filter()、some()、every()
  • 原文地址:https://www.cnblogs.com/TaoR320/p/12680152.html
Copyright © 2020-2023  润新知