黑马程序员——多线程4:再谈单例设计模式

------- android培训java培训、期待与您交流! ----------

       我们在前面的博客《设计模式1:单例设计模式》(简称《设计模式1》,下同)一文中简单介绍过单例设计模式以及两种实现方式——饿汉式与懒汉式。关于单例设计模式的由来、应用背景和饿汉式的实现方式等内容不再这里赘述,我们主要说一说懒汉式的线程安全问题,及其解决方法。

1. 懒汉式的线程安全问题

我们在《设计模式1》中曾经谈到过,未考虑线程安全内的懒汉式代码如下,

代码1:

class Single
{
	private static Single s = null;
	private Single(){}
 
	public static Single getInstance()
	{
		if(s == null)
			s= new Single();
		return s;
	}
}
        当多个线程同时执行getInstance方法时,由于CPU分配执行权的随机性,可能1号线程执行完if判断语句以后,执行权被分配给2号线程,而当2号线程执行完全部语句最终返回Single对象以后,1号线程才得以继续运行,由于1号线程已经执行过if语句,所以它将再次创建Single对象并返回。这样就不能保证创建对象的位移性了。

2. 问题解决方法

        对于上述问题的解决方法,《设计模式1》中也介绍过两种,而其中同步函数的执行效率较低因此不建议使用,主要讨论第二种方法——同步代码块。

        首先,getInstance方法中的三个语句均涉及到对Single对象的操作,因此按理应将它们全都定义在同步代码块内,不过,最后一句返回Single对象的语句,并不涉及到线程安全问题,因此没有必要定义到同步代码块内。

        其次,如果仅仅是将这两个语句定义到同步代码块内,它的效果与同步函数其实是没有区别的。因此,为了尽可能减少判断“锁”的次数,工程师门想出了一个很好的办法——在同步代码块外再嵌套一个if语句判断类类型变量s是否有所指向。最终的代码如下,

代码2:

class Single
{
	private static Single s;
	private Single(){}
	public static Single getInstance()
	{
		//双重if判断,减少判断“锁”的次数,提高效率
		if(s == null)
		{
			/*
				由于静态方法中无法使用关键字this
				因此同步代码块中的“锁”就不能使当前对象
				此时我们就可以使用本类的字节码文件对象作为“锁”
			*/
			synchronized(Single.class)
			{
				if(s == null)
					s = new Single();
			}
		}
		return s;
	}
}
        我们假设有A、B、C三个线程同时执行getInstance方法。当A线程执行完同步代码块内的if语句后执行权被分配给B线程,而B线程止步于同步代码块外,此时A线程再次获得执行权,创建对象并返回,此时线程B判断if语句为假不再创建新对象,仅返回唯一对象。最后C线程再执行getInstance方法时,第一个if语句的判断结果为假,直接返回唯一对象,这样就在最大限度条程序运行效率的前提下,保证了线程安全。

        那么无论是从代码的复杂程度和上述过程的描述来看,懒汉式比饿汉式复杂的多,而效果相同,因此实际开发建议使用饿汉式,而目前懒汉式多用于面试中。

3. 懒汉式面试问答

1)  问题一:饿汉式与懒汉式有什么区别?

答:懒汉式相对于饿汉式的特点在于它的延时加载。

2)  问题二:懒汉式是否存在线程安全问题?

答:存在。因此可以通过同步机制解决。但是同步函数效率较低,通常使用双重if判断加同步代码块的方法解决。

3)  问题三:如果使用同步代码块,它的“锁”应是什么对象?

答:本类字节码文件对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值