单例模式

1.1创建型模式

创建型模式(Creation Pattern)是对类的实例化过程的抽象化,能够提供的对象的创建和管理职责,设计模式中创建型模式共有5种:

  • 单例模式
  • 工厂方法
  • 抽象工厂模式
  • 建造者模式
  • 原型模式


1.2单例模式(Singleton Pattern)

单例模式关注的是对象创建的次数和何时创建对象,对象创建的次数正如其名是1个,何时创建则衍生出单例模式的两种形式,饿汉模式和懒汉模式。

引用一句地道的英文介绍单例模式:Ensure a class has only one instance, and provide a global point of access to it.  

饿汉模式:类加载的时候就创建对象。

懒汉模式:第一次引用的时候创建对象。


1.3饿汉式单例模式

/**
 * @function 饿汉式单例模式
 * @date2014年7月18日
 * @author diaorenxiang
 */
public class HungrySingleton {
	
	private static HungrySingleton instance = new HungrySingleton();
	// 私有构造方法,保证外界无法实例化该对象
	private HungrySingleton() {
		
	}
	// 通过该方法获得实例
	public static HungrySingleton getInstance() {
		return instance;
	}
}

由于一个类在一个ClassLoader中只会被初始化一次,所以在ClassLoader加载HungrySingleton类后,instance实例就被创建,不存在并发的问题。但是在一些情况下饿汉式单例模式是无法使用的,比如instance实例的创建是依赖于后期的参数或者配置文件的。

如何改进饿汉式单例模式,使它实现“懒加载”,可利用私有静态内部类来实现实例的“懒初始化”,下面是改进后的方案

/**
 * @function 改进的饿汉式单例模式
 * @date2014年7月18日
 * @author diaorenxiang
 */
public class Singleton {

	private Singleton() {
		
	}
	
	public static class SingletonHolder {
		static final Singleton instance = new Singleton();
	}
	
	public static Singleton getInstance() {
		return SingletonHolder.instance;
	}
	
}

其实,这样的改进,饿汉式单例模式实际上就变成了懒汉式,利用的是同一个类只能被一个ClassLoader加载一次,而且不会存在并发问题,当加载Singleton类的时候并不会初始化实例,因为所在的SingletonHolder没有被主动使用,只有首次调用getInstance( )方法的时候,才会得到instance实例。


1.4懒汉式单例模式

/**
 * @function 懒汉式单例模式
 * @date2014年7月18日
 * @author diaorenxiang
 */
public class LazySingleton {

	private static LazySingleton instance = null;
	
	private LazySingleton() {
		
	}
	//方法同步
	public synchronized static LazySingleton getInstance() {
		if(instance == null){
			instance = new LazySingleton();
		}
		return instance;
	}
}

这种方式的单例模式通过同步方法去获取实例,虽然保证了线程安全,但是只有在第一次调用创建实例的时候需要同步,以后只需要直接返回实例不需要同步,所以其效率低下。而且即使把同步方法改成方法内的同步块也无济于事。

改进的方式是使用双重检查锁(DCL,Double Control Locking),改进后的懒汉式单例模式如下:

/**
 * @function 使用双重检查锁的懒汉式单例模式
 * @date2014年7月18日
 * @author diaorenxiang
 */
public class LazySingleton {

	private static LazySingleton instance = null;
	
	private LazySingleton() {
		
	}
	
	public static LazySingleton getInstance() {
		if(instance == null){                           // Point1
			synchronized (LazySingleton.class) {
				if(instance == null) {
					instance = new LazySingleton();  //Point2
				}
			}
		}
		return instance;
	}
}

上述方案看似已经是正确的方案,但是如果考虑到类加载初始化过程和JVM内存模型,还是存在一些问题,这一问题的原因是因为上述注释Point2处的操作不是原子性的。例如,如果线程A在point 1处检查到instance为空,接着它获取锁,创建对象,在创建对象初始化过程中instance不为null,这对其它线程可见,但是线程A初始化instance并未完成,如果这个时候线程B到达注释point 1处,检查instance不为空,直接返回引用该未初始化完成的对象,从而导致系统崩溃。

对于DCL的总结:DCL的真正问题在于,它是基于这样假设的:当没有使用同步时读取一个共享变量,可能发生的做坏情况不过是错误地看到过期值(这里是null);这种情况下DCL通过占有锁后再检查一次,希望这样能够避免风险。但是最坏的情况可能比这还糟糕——线程可能看到对象的无效状态(或者残缺状态)。

在JDK1.5之后的版本,可以使用以下的方案解决:

/**
 * @function 使用双重检查锁的懒汉式单例模式
 * @date2014年7月18日
 * @author diaorenxiang
 */
public class LazySingleton {

	private volatile static LazySingleton instance = null;
	
	private LazySingleton() {
		
	}
	
	public static LazySingleton getInstance() {
		if(instance == null){
			synchronized (LazySingleton.class) {
				if(instance == null) {
					instance = new LazySingleton();
				}
			}
		}
		return instance;
	}
}
只是加上了一个关键字volatile,它保证了多个线程正确处理单个实例。为什么仅仅使用volatile之后,就能够保证多线程执行的顺序?这得益于JMM(Java内存模型)为所有程序内部的动作定义了一个偏序关系(也就做happens-before),happens-before保证了动作的执行次序,其中有一条就是对于volatile域的写操作happens-before于每个后续线程对该域的读操作。


1.5单例模式的优点

由于内存中只存在一个实例,减少了内存的开支,并且对象的创建和销毁会带来很大的系统性能开销,单例模式对象常驻内存,很好的解决了这项开销。

单例模式可以设置全局的访问点,优化和共享访问。


1.6单例模式的缺点

无法创建子类,扩展困难,除非修改代码


1.7单例模式的使用场景

  • 要求生成唯一序列号的环境
  • 在整个环境中需要一个共享的访问点或者共享数据。例如,web页面的计数器。
  • 创建一个对象需要太多的系统资源,例如访问IO和创建数据库连接等
  • 需要定义大量静态常量和静态方法的环境。

另外,使用单例模式的时候,需要注意序列化和克隆对唯一性的影响。如果一个单例类实现了Serializable或者Cloneable接口,则可能被反序列化或者克隆出一个新的实例出来,从而破坏了唯一性的要求,因此,单例类最好不要实现Serializable和Cloneable接口,当然如果使用反射去破坏唯一性,那也没辙。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值