面试经常被问的单例模式到底长啥样

饿汉式和懒汉式

单例模式书中的官方定义是:保证一个类仅有一个实例,并提供一个访问它的全局访问节点。

解释:就是一个类除了实例化一个对象之外保证没有其他实例化可以被创建,并且它可以提供一个访问该实例的方法。

单例模式结构图:

所以我们可以明确知道单例模式就是一个类,但这个类有点特殊,特殊在他只能创建一个实例,而对外就是提供一个外界可以访问的实例化方法。

但是我们如何保证让它只能实例化一个对象呢?可以看看下面这样一个代码。

// 懒汉式
class Single
{
private:
	Single() { } // 构造函数
public:
	static Single* inistance()  // 这里为啥是静态函数:非静态成员必须与特定对象对应
	{
		if (sig == nullptr)
			sig = new Single;
		return sig;
	}
private:
	static Single* sig;	// 这里为啥是静态成员:非静态成员必须与特定对象对应
};
Single* Single::sig = nullptr; // 静态成员必须类外进行初始化

这里声明了一个Single的单例类,类中有两个与其它类不同的地方,一个就是它的够着函数是私有的,这样做的目的就是防止外界调用构造函数,而只要外界不能调用构造函数那么就不能对该类进行实例化。那么这样的话我们怎么对它实例化对象呢?这就是Single中inistance()函数存在的意义,它的作用就是对一个没有实例化的对象进行实例化,如果该对象已经实例化了,这if条件就不会满足,从为保证了实例化的对象唯一。而这里为什么是static的原因就是,如果它不是静态的话你可以想想会发生什么?是不是外界无法调用inistance()这个函数?(因为外界要想实例化一个对象就必须要调用inistance()成员函数,而inistance是普通成员函数的话你在没有实例对象的情况下是不能够调用的),sig为静态的原因是静态成员函数只能调用静态成员。

到这里可以说是已经实现了一个基本的单例类。而这只是单例类的一种叫懒汉式的写法,下面来介绍一种恶汉式的写法。

// 饿汉式
class Single
{
private:
	Single() { }
public:
	static Single* inistance()  // 这里为啥是静态函数:非静态成员必须与特定对象对应
	{
		return sig;
	}
private:
	static Single* sig;	// 这里为啥是静态成员:非静态成员必须与特定对象对应
};
Single* Single::sig = new Single; // 实例化对象来进行初始化

饿汉式与懒汉式的区别就是饿汉式不需要在inistance()内判断和实例化对象,而是在静态成员sig初始化的时候就进行初始化。这样就不需要再去对sig是否实例化进行判断了,所以比懒汉式更加安全。

多线程中的懒汉式

在多线程中上面的懒汉式明显是存在问题的,比如当两个线程同时执行到if (sig == nullptr)语句且条件为true时,然后两个线程都会去执行sig = new Single;这一句代码,这就违反了单例只能实例化一个对象的规定。所以下面是对懒汉式改进后的结果:

// 懒汉式
class Single
{
private:
	Single() { }
public:
	static Single* inistance()  // 这里为啥是静态函数:非静态成员必须与特定对象对应
	{
		// 多线程优化
		if (sig == nullptr)
		{
			unique_lock<mutex> qlock(mtx); // 上锁
				if (sig == nullptr)
					sig = new Single;
		}
		return sig;
	}
private:
	static mutex mtx;
	static Single* sig;	// 这里为啥是静态成员:非静态成员必须与特定对象对应
};
Single* Single::sig = nullptr;

unique_lock<mutex> qlock(mtx)是确保当一个线程位于代码的临界区时,另一个线程不进入临界区。如果其它线程试图进入锁定的代码,则它将一直处于等待状态,直到该对象被释放为止。而外面再加一层是否为空的判断是为了保证执行unique_lock<mutex> qlock(mtx)的时候确保当前的对象时没有被实例过的,因为上锁操作对性能是有影响的,所以这里会有一个双重的判定。

单例模式内存释放问题

对于单例模式我们开辟的内存其实不用释放,原因是单例对象在内存中只保存了一份所以对空间占用不是很高,还有单例对象实例的对象我们一般都会经常用到所以最好还是不要释放,但是如果你确实需要对实例的对象进行释放的话我们可以在类中再增加一个类,该类就类似于智能指针的作用,专门用来负责单例对象的释放。

单例模式优缺点

优点:

  • 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
  • 避免对资源的多重占用(比如写文件操作)。

缺点:

  • 没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

单例应用场景

  1. Windows系统的任务管理器。
  2. Windows系统的回收站。
  3. 操作系统的文件系统,一个操作系统只能有一个文件系统。
  4. 数据库连接池的设计与实现。
  5. 多线程的线程池设计与实现。

总结

单例无论是在项目的开发中,还是面试中都是非常常见的,因此需要熟练的掌握好单例的知识。最后这就是单例模式的全部内容了,回头看看不是很复杂吧!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值