不是你记忆中的单例模式,但适用的程度,更胜一筹

在这里插入图片描述

故事线

我有一个好朋友广军,为了体验生活,在学校旁边盘了个店面,开了家奶茶汉堡店。
店面新开张,首先要作的事儿就是去工商局登记一下,而后作个章。web

到了工商局,人家给他来了个友情提示:XXX只为私密文件,请不要给别人乱玩,出了啥事儿你须要负责。数据库

登记完,作了章,作个店就算是开起来了,他开始当上了店长,刚开始事情多啊,他又要招聘,又要采购,又要宣传,又要会计算帐···
可是这些事儿也只能他来干,亲力亲为,先把这个店扶起来。缓存

单例模式

那这个故事就很好的契合了单例模式的应用场景,因此我这个朋友想和你聊聊单例模式。安全

什么是单例模式呢?
在项目中,有些类是须要对它们进行“计划生育”的,即这个类只能有一个实例,若是出现多个实例则会有数据不一致的风险。
你说我开个店,要是有两个老板,那今天又个单,我说签,他又跟人家说不签;这个员工不积极,我说开,他又说不开···那岂不是乱套?多线程

单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。并发

巧了,这个模式只有一个类,叫单例类,因此类图我就不画了吧。svg

单例模式的应用场景举例:牵扯到数据问题,数据库首当其冲,缓存天然也跑不了。函数

单例类代码实现

//这里是.h文件
//老板类单例
class Single_Boss
{ 
 
  
public:
    static Single_Boss *instence();//获取数据库单例
//重点在这个函数
	void run();
private:
    Single_Boss();
    ~Single_Boss();
    char *errmsg;

    static Single_Boss *Boss;//实例
};
//源文件

Single_Boss *Single_Boss::Boss= NULL;

Single_Boss::Single_Boss()
{ 
 
  
    cout << "Big Boss" << endl;	//debug
}

Single_Boss::~Single_Boss()
{ 
 
  
    cout<<"Seeyou Boss"<<endl;
}

Single_Boss* Single_Boss::instence()
{ 
 
  
    if(!Boss)
    { 
 
  
        Boss= new Single_Boss();
    }
    	return Boss;
}

void Single_Boss::run(){ 
 
  
	cout<<"你好,欢迎光临XXX,请问须要点什么服务?"<<endl;
}

int main()
{ 
 
  
	//Single_Boss *boss = new Single_Boss(); //不信的话大能够将这一行放出来,下面那行屏蔽掉试试
    Single_Boss *boss= Single_Boss::instence();	//这是在类外使用单例
    
    boss->run();
    
    return 0;
}

提高部分

多线程下的单例模式

曾经有一份真挚的数据库摆在我眼前,惋惜我没有去珍惜它,直到个人项目屡屡崩溃,我才知道,若是能重来,我要加个锁。。。性能

俱往矣,数风流人物,还看今朝。测试

来咱们从新审视一下下面这段代码:

Single_Boss* Single_Boss::instence()	//1
{ 
 
  										
    if(!Boss)							//2
    { 
 
  									
        Boss= new Single_Boss();		//3
    }									
    return Boss;						
}

若是在多线程状况下,一旦有两个线程同时进入了 2 ,怎么办?这不是十分正常的事情吗?一点防范都没有,这不是送人头的行为吗?
白给!!

因此,改一下:

Single_Boss* Single_Boss::instence()	//1
{ 
 
  					
	lock(db_mutex);	//假设这个锁我已经初始化过了 
    if(!Boss)							//2
    { 
 
  									
        Boss= new Single_Boss();		//3
    }				
    unlock(db_mutex);	//上锁和解锁必定要同时写,就算忘记写中间步骤,也要先写解锁 
    
    return Boss;						
}

这样写,可还行?有没有 慧眼识猪 的朋友在下面评论区call个“1”?

这样写的话,每次使用数据库以前都要进行加锁操做,虽然安全了,可是大大地提升了负荷。

因此,再改一下:

Single_Boss* Single_Boss::instence()	//1
{ 
 
  				
	if(!Boss){ 
 
  			//一重锁定
		lock(db_mutex);				
	    if(!Boss)		//二重锁定 
	    { 
 
  									
	        Boss= new Single_Boss();		
	    }				
	    unlock(db_mutex);	//上锁和解锁必定要同时写,就算忘记写中间步骤,也要先写解锁 
    }
    return Boss;						
}

看到这里,可能有的朋友会疑惑:直接把上面那个的 if 判断和锁的位置换一下不就完事儿了吗,为何要在外面再加上一层,这不是画蛇添足吗?

仍是那个问题:
若是你有两个线程,突破了第一层 if 的防线,及时一个线程会被卡在锁的外面,可是锁仅仅只是锁住了建立单例的部分,当拿到锁的那个线程释放了锁,另外一个线程不是照样能拿到锁,建立它的“单例”,那这个锁还有什么意义呢?

而在锁内锁外都加一层 if 判断,当第一个线程进入锁空间,建立完单例,后面的线程即便是拿到了锁,也不会去执行建立单例的步骤。

这,才是一个好的单例模式,这是单例模式中的“懒汉模式”。

饿汉式

有懒汉式,那也有个饿汉式单例。

什么是饿汉式呢?饿汉模式的关键:初始化即实例化

微调上面的代码:

Single_Boss *Single_Boss::Boss= new Single_Boss();

Single_Boss* Single_Boss::instence()
{ 
 
  
//不须要进行实例化
    //if(!Boss)
    //{ 
 
  
    // Boss= new Single_Boss();
    //}
   	return Boss;
}

通常饿汉式加载所致使的弊端是可能我并不想使用实例可是实例已经被构造,相对于懒汉式的用则构造会形成内存的浪费,可是其实现方式很简单,不用人为加锁保证线程安全。

懒汉仍是饿汉?

选哪一个能够看我的喜爱吧,这里给出一点建议:

懒汉:在访问量较小时,采用懒汉实现。这是以时间换空间。

饿汉:因为要进行线程同步,因此在访问量比较大,或者可能访问的线程比较多时,采用饿汉实现,能够实现更好的性能。这是以空间换时间。

单例模式的优缺点

优势

  • 因为单例模式在内存中只存在一个对象,减小了内存的开支,特别是当对象须要频繁的建立、销毁时,并且建立或销毁时性能又没法优化,单例模式的优点就很是明显。
  • 单例模式能够避免对内存的多重占用。
  • 单例模式能够在系统设置全局的访问点,优化和共享资源访问。这招我常常用,也很喜欢,由于确实方便,作一个标志位单例类,负责全部数据表的映射处理。(要了解能够私信我)

缺点

  • 单例模式通常没有接口,难以拓展。若是要拓展,考虑重构。
  • 单例模式对于测试是不利的。在并发环境中,若是单例没有完成,是不能进行测试的。

还行吧。

创做不易,顺手收藏好习惯,划着划着,就找不到了。

在这里插入图片描述