设计模式之单例模式

       在面向对象的世界中,一个类总是会实例化很多对象,以解决具体问题。但Singleton说:“我是独一无二的,我在任何时刻都只有一个对象。”Excuse me?Who are you?(单例模式实现及例子的代码均在git)


1. 单例模式

       单例模式确保某个类只有一个实例,并提供一个全局访问点。在计算机系统中,线程池(threadpool)、缓存(cache)、注册表(registry)、日志对象、打印机、显卡等设备的驱动程序的对象常被设计成单例。因为这类对象只能有一个实例,如果制造出多个实例,就会导致许多问题产生,例如:程序的行为异常、资源使用过量,或者是不一致的结果。

单例模式有以下特点:
  1、单例类只能有一个实例。
  2、单例类必须自己创建自己的唯一实例。
  3、单例类必须给所有其他对象提供这一实例。

public class Singleton {   //经典的单例模式的实现(未同步)
	private static Singleton instance = null;
	
	private Singleton(){}  //把构造器声明为私有
	
	public static Singleton getInstance(){
		if(instance == null){
			instance = new Singleton();
		}
		return instance;
	}
}

2. 巧克力工厂(例子)

       下面以例子来说明单例模式类的使用。

1.原始巧克力锅炉

       描述:为了避免面锅炉满了继续放原料或锅炉空烧的情况,代码写得相当小心

public class ChocolateBoiler {   //巧克力锅炉
	private boolean empty;       //是否为空
	private boolean boiled;      //原料是否已煮沸
	
	public ChocolateBoiler(){
		empty = true;
		boiled = false;
	}
	
	public void fill(){  
		if(isEmpty()){
			empty = false;
			boiled = false;
			/*在锅炉内填满巧克力和牛奶的混合物*/
		}
	}
	
	public void drain(){
		if(!isEmpty() && isBoiled()){
			/*排出煮沸的巧克力和牛奶*/
			empty = true;
		}
	}
	
	public void boil(){
		if(!isEmpty() && !isBoiled()){
			/*加热巧克力和牛奶*/
			boiled = true;
		}
	}
	
	public boolean isEmpty(){
		return empty;
	}
	
	public boolean isBoiled(){
		return boiled;
	}

}

2.) 单例巧克力锅炉

       尽管这家巧克力工厂在有意识地防止不好的事情发生,但是如果同时有多于一个的ChocolateBoiler实例存在,一台巧克力锅炉可能会导致操作结果覆盖,如"同时"在加热和放原料。故需把巧克力锅炉改为单例类。

public class ChocolateBoiler_S {   //单例巧克力锅炉
	private boolean empty;
	private boolean boiled;
	private static ChocolateBoiler_S instance = null;
	//将构造方法改为私有
	private ChocolateBoiler_S(){
		empty = true;
		boiled = false;
	}
	//返回单例锅炉
	public static ChocolateBoiler_S getInstance(){
		if(instance == null){
			instance = new ChocolateBoiler_S();
		}
		return instance;
	}
	
	public void fill(){  
		if(isEmpty()){
			empty = false;
			boiled = false;
			/*在锅炉内填满巧克力和牛奶的混合物*/
		}
	}
	//其他方法省略不列出来
}

3.) 线程安全的单例巧克力锅炉

       在多线程的情况下会导致锅炉加热时也能继续加入原料,因为代码中并没有同步化,可能有两个线程同时进入到实例化(getInstance)的方法中,导致线程不安全。

       只要把getInstance()变成同步(synchronized)方法,多线程灾难几乎就可以轻易解决了。

public class ChocolateBoiler_S {   //单例巧克力锅炉
	private boolean empty;
	private boolean boiled;
	private static ChocolateBoiler_S instance = null;
	//将构造方法改为私有
	private ChocolateBoiler_S(){
		empty = true;
		boiled = false;
	}
	//返回单例锅炉,同步getInstance()方法
	public static synchronized ChocolateBoiler_S getInstance(){
		if(instance == null){
			instance = new ChocolateBoiler_S();
		}
		return instance;
	}
	
	public void fill(){  
		if(isEmpty()){
			empty = false;
			boiled = false;
			/*在锅炉内填满巧克力和牛奶的混合物*/
		}
	}
	//其他方法省略不列出来
}
       嗯,多线程灾难几乎就可以轻易解决了。等等,几乎?

       只有第一次执行此方法时,才需要真正同步,即一旦设置好instance变量,就不再需要同步这个方法了。之后每次调用这个方法,同步都是一种累赘,会使程序执行效率下降。


3. 单例模式(多线程改善)

       为了要复合大多数Java应用程序,我们需要确保单例模式能在多线程的状况下正常工作。但是似乎同步getInstance()的做法将拖垮性能,该怎么办呢?

1.) 懒汉式单例(延迟实例化)

       如果应用程序可以接受getInstance()造成的额外负担(如使用不频繁),就直接使用这个懒汉式的同步单例,因为同步getInstance()的方法既简单又有效。
public class Singleton {
	private static Singleton instance = null;
	
	private Singleton(){}  //把构造器声明为私有
	
	public static synchronized Singleton getInstance(){
		if(instance == null){
			instance = new Singleton();
		}
		return instance;
	}

}

2.)饿汉式单例(急切实例化)

       如果应用程序总是创建并使用单件实例,或者在创建和运行时方面的负担不太繁重,你可能想要急切(eagerly)创建此单件。利用以下的这种做法,我们依赖JVM在加载这个类时马上创建此唯一的单例。 JVM保证在任何线程访问instance静态变量之前,一定先创建此实例。
public class Singleton {
	/*在静态初始化器中创建单例,保证了线程安全*/
	private static Singleton instance = new Singleton(); 
	
	private Singleton(){}  //把构造器声明为私有
	
	public static synchronized Singleton getInstance(){
		return instance;
	}

}

3.)双重检查加锁

        利用双重检查加锁(double-checked locking), 首先检查是否实例已经创建了,如果尚未创建,“才”进行同步。这样一来,只有第一次会同步,这正是我们想要的。
public class Singleton {
	private volatile static Singleton instance;  //volatile保证多个线程不缓存此变量
	
	private Singleton(){}  //把构造器声明为私有
	
	public static Singleton getInstance(){
		if(instance == null){
			synchronized(Singleton.class){
				if(instance == null){    //进入区块后,再检查一次。仍是null才创建实例
					instance = new Singleton();
				}
			}
		}
		return instance;
	}
}


参考资料:

1.《Head First设计模式》

2.http://blog.youkuaiyun.com/jason0539/article/details/23297037/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值