在面向对象的世界中,一个类总是会实例化很多对象,以解决具体问题。但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.) 懒汉式单例(延迟实例化)
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.)双重检查加锁
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/