【设计模式】单例模式 && 谈谈双锁机制的弊端

单例模式改善了全局变量和命名空间的冲突,可以说是一种改良了的全局变量。这种一个类只有一个实例,且提供一个访问全局点的方式,更加灵活的保证了实例的创建和访问约束。系统中只有一个实例,因此构造方法应该为私有。当然单例是实例有限例的特例,可以改变实例得数目,通过计数等方式可做到双例模式,三例模式。。。

单例的实现分为:
饿汉式:类加载时直接创建静态实例 
懒汉式:第一次需要时才创建一个实例,那么newInstance方法要加同步。
饿汉式比懒汉式要好,尽管资源利用率要差。但是不用同步。


一、饿汉模式

public class Singleton_Simple {
	
	private static final Singleton_Simple simple = new Singleton_Simple();
	
	private Singleton_Simple(){}
	
	public static Singleton_Simple getInstance(){
		return simple;
	}

}

二、.懒汉模式

public class Singleton_lazy {

	private static Singleton_lazy lazy = null;
	
	private Singleton_lazy(){}
	
	public static synchronized Singleton_lazy getInstance(){
		if( lazy == null ){
			lazy = new Singleton_lazy();
		}
		return lazy;
	}
}

 说明:懒汉模式是相对于饿汉模式而言的,在jvm进程启动并在我们主动使用该类的时候不会在内存中初始化一个单例对象,只有当我们调用getInstance()的时候才去创建该对象,他的创建是在我们调用getInstance()静态方法之后,为了并没现象同步问题,我们在getInstance()方法上加了一个锁,这个方法每次只允许一个线程进来,虽然同步问题是解决了,但是相应的性能问题就出现了。


三、双锁机制

public class Singleton_DoubleKey {

	private static Singleton_DoubleKey doubleKey = null;
	
	private Singleton_DoubleKey (){}
	
	public static Singleton_DoubleKey getInstance(){
		if( doubleKey == null ){    //①
			synchronized(Singleton_DoubleKey.class){	//②
				if( doubleKey == null ){	//③
					doubleKey = new Singleton_DoubleKey();  //④
				}
			}
		}
		return doubleKey;
	}
}

说明:双锁机制的出现是为了解决前面同步问题和性能问题,看上面的代码,简单分析下确实是解决了多线程并行进来不会出现重复new对象,而且也实现了懒加载,但是当我们静下来并结合java虚拟机的类加载过程我们就会发现问题出来了,对于JVM加载类过程不熟悉的,这里我简单介绍下,熟悉的跳过这段(当然,既然你熟悉就自然会知道双锁的弊端了)。
jvm加载一个类大体分为三个步骤:
加载阶段:就是在硬盘上寻找java文件对应的class文件,并将class文件中的二进制数据加载到内存中,将其放在运行期数据区的方法区中去,然后在堆区创建一个java.lang.Class对象,用来封装在方法区内的数据结构;
连接阶段:这个阶段分为三个步骤,步骤一:验证,验证什么呢?当然是验证这个class文件里面的二进制数据是否符合java规范咯;步骤二:准备,为该类的静态变量分配内存空间,并将变量赋一个默认值,比如int的默认值为0;步骤三:解析,这个阶段就不好解释了,将符号引用转化为直接引用,涉及到指针,这里不做多的解释;
初始化阶段:当我们主动调用该类的时候,将该类的变量赋于正确的值(这里不要和第二阶段的准备混淆了),举个例子说明下两个区别,比如一个类里有private static int i = 5; 这个静态变量在"准备"阶段会被分配一个内存空间并且被赋予一个默认值0,当道到初始化阶段的时候会将这个变量赋予正确的值即5,了解了吧!


好了,上面大体介绍了jvm类加载过程,回到我们的双锁机制上来分析下问题出在了哪里?假如有两个并发线程a、b,a线程主动调用了静态方法getInstance(),这时开始加载和初始化该类的静态变量,b线程调用getInstance()并等待获得同步锁,当a线程初始化对象过程中,到了第二阶段即连接阶段的准备步骤时,静态变量doubleKey 被赋予了一个默认值,但是这时还没有进行初始化,这时当a线程释放锁后,b线程判断doubleKey != null,则直接返回了一个没有初始化的doubleKey 对象,问题就出现在这里了,b线程拿到的是一个被赋予了默认值但是未初始化的对象,刚刚可以通过锁的检索!


所以对于上面的几个模式还是推荐使用第一种,在jvm加载类的时候就初始化一个对象,也避免了同步问题。






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值