5个单例模式写法的解析

1 篇文章 0 订阅
1 篇文章 0 订阅

单例模式是Gof的23种模式中比较简单的一种,主要运用于一个类只能有一个对象的情况(例:实验室有多台电脑连接同一台打印机,这时这台打印机就是唯一的一个实例,无论哪一台电脑要打印都要通过这个打印机来进行)。

而为了达到保证整个系统该类只有一个实例的目的,各路IT大牛们给出了各种不同的实现方法。这里给大家展示其中比较出名的5种。

1、饿汉模式:

/**饿汉模式
 * 在java中对类的全局变量的声明同时进行赋值的语句在类的字节码被载入时便被执行并保持到静态内存区域里*/
public class Singleton{
	private static Singleton instance=new Singleton();//声明并生成一个实例对象(在)
	private Singleton(){
		System.out.println("初始化Singleton");
	}
	public static Singleton getInstance(){//直接返回已生成的对象
		return instance;
	}
}
饿汉模式的思路是在一开始就生成一个对象实例,然后无论谁调用这个类的getInstance()方法都直接返回这个实例,从而来保证所以用户获取到的都是同一个实例。而为了避免用户通过直接调用类的构造函数的方法来new一个实例,所以所有单例模式的构造函数都是私有的。

饿汉模式的优点是实现思路和实现过程简单,而缺点则是无论该单例模式的类是否被调用,都需要生成一个实例,而这在实例占用空间大或过程复杂而用到的几率又很小的情况下就会显得和浪费。

2、懒汉模式:

/**懒汉模式*/
public class Singleton {
	private static Singleton instance;//声明一个自身的实例,但不做初始化
	private Singleton(){ //将构造函数置为private,避免其他类中通过new的方式获取实例
		System.out.println("初始化Singleton");
	}
	public static synchronized Singleton getInstance(){//同步该方法,避免多个其他
		                                               //对象同时调用该方法,产生多个instance对象
		if (instance==null) {//只有当实例不存在的时候才新建一个实例
			instance=new Singleton();
		}
		return instance;
	}
}
懒汉模式是对饿汉模式缺点的一种改进,只有需要调用类的实例时才初始化一个实例,这样就可以有效的克服饿汉模式的缺点了。不过这样就造成在未创建实例时,多个用户同时使用getInstance()方法获取实例的情况下可能“同时”生成多个不同实例的情况(每个用户都可以看成一个线程,在不进行同步的情况下当线程1执行完if(instance==null)后cpu被分配给线程2,这时线程2执行到if(instance==null)时因为实例还没有创建,所以创建一个实例。之后,当线程1执行的时候,因为已经完成判断,所以不再判断直接创建一个实例。这样就导致系统中出现不同的两个实例。),为了避免多个用户同时调用getInstance()方法产生多个实例的情况,该模式下将getInstance()方法加了synchronized锁,保证其原子性。这样问题是解决了,但是每次调用getInstance的时候都要求同步,而同步的用时是不同步的几倍,所以在频繁调用的情况下将会严重影响到系统的性能。

3、双重核验锁

/**双重核验锁*/
public class Singleton{
	private volatile static Singleton instance;
	private Singleton(){
		System.out.println("初始化Singleton");
	}
	public static Singleton getInstance(){
		if (instance==null) {
			synchronized(Singleton.class){
				if (instance==null) {
					instance=new Singleton();
				}
			}
		}
		return instance;
	}
}
为了避免每次调用getInstance()的时候都要进行同步,于是有人设计出来这种先判断实例是否已经初始化如果没有再加锁的模式。这样在实例初始化后就不用再同步了。这边需要注意的是,默认情况下当一个线程获取一个对象实例的时候,会将该实例拷贝到CPU缓存中对该实例进行操作,只要CPU缓存没有刷新会主存那么这个实例的修改对于其他线程就是不可见的,也就是说如果在刷新之前其他线程获取的实例是未修改前的实例,这样就好导致数据不一致的问题。为了解决这个问题,instance必须声明为volatile类型来保证线程每次总是从主存中读取数据并且每次修改完数据后都会将数据刷新回主存中。

4、静态内部类

/**静态内部类*/
public class Singleton{
	private static class SingletonHander{
		private static final Singleton INSTANCE=new Singleton();
	}
	private Singleton(){
		System.out.println("初始化Singleton");
	}
	public static Singleton getInstance(){
		return SingletonHander.INSTANCE;
	}
}
换个思路,其实采用静态内部类也可以解决1和2的问题,而且不需要使用的锁(synchronized)和线程可见机制(Volatile)。静态内部类是一种不依赖于外部类对象而只依赖于外部类的一种类。而编译之后内部类和外部类的二进制码分别存放在不同的class文件中。



在没有主动调用的情况下内部类不会进行初始化,只有当getInstance被调用时,SingletonHandler类才会初始化INSTANCE。而且由于INSTANCE是final类型的也就是常量,所以在整个过程中该值都不会改变,从而达到保证唯一性的目的。

5、枚举类型

/**枚举模式*/
public enum Singleton{
	INSTANCE;//只定义一个枚举项(在枚举类中每一个枚举项对应一个实例)
	private Singleton() {
		// TODO Auto-generated constructor stub
		System.out.println("初始化Singleton");
	}
	public void doAnyThingYouWant(){
		System.out.println("类的内容");
	}
}
其实前面介绍的4种写法都有一个严重的缺陷,那就是一旦使用反射机制或者系列化和反系列化的方式对实例进行复制,那么这些单例模式也就没有什么作用了。而采用枚举类型实现单例模式,因为其自身就是唯一性的,而为了达到这种唯一性(可以参考我写的另一篇博文: Enum源码分析)而采取了相应的措施来阻止各种复制,所以能够保证通过该方式得到的一定是唯一的存在。

个人还是比较喜欢使用枚举的方式来实现单例模式的,比较相对于其他几种模式它的实现更加简单和安全。当然,在实际开发中我们还是要根据实际情况来决定我们使用什么方法,活学活用是我们必须掌握的一项技能!希望这篇博文对你有所帮助,如果有什么意见和建议欢迎批评指正,谢谢你的浏览!


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值