@XingdingCAO
2017-11-24T19:06:49.000000Z
字数 4160
阅读 2063
Java
singleton
constructor
singleton
就是一个仅仅实例化一次的类(在任何时期,内存中都应该仅存在一个实例),称做:单例。单例通常代表了一个在本质上数量保持唯一的系统组件,如:窗口管理器、文件系统。
演进部分引自:http://wuchong.me/blog/2014/08/28/how-to-correctly-write-singleton-pattern/
先不去看《Effective Java》中介绍的单例模式,先看一下单例模式的演进过程。
//DON'T DO THIS
public class Singleton{
private Singleton(){}
private static Singleton INSTANCE;
public static Singleton getInstance(){
if(INSTANCE==null)
INSTANCE = new Singleton();
return INSTANCE;
}
}
public class Singleton{
private Singleton(){}
private static Singleton INSTANCE;
public static synchronized Singleton getInstance(){
if(INSTANCE==null)
INSTANCE = new Singleton();
return INSTANCE;
}
}
synchronized
修饰,互斥地访问方法,就可以保证线程安全。getInstance
方法,确保单例,非常不错。但是,创建完毕之后呢,互斥地去取得一个不变类的实例的引用,十分影响性能,而且没有必要。
public class Singleton{
private Singleton(){}
private static Singleton INSTANCE;
public static Singleton getInstance(){
if(INSTANCE==null){
synchronized(Singleton.class)
if(INSTANCE==null){
INSTANCE = new Singleton();
}
}
return INSTANCE;
}
}
double-check
,在创建单例后不再互斥地访问。唯一要注意的一点是,volatile
关键字不能少,而且使用Java 5及以上版本。这个关键字在本例中的作用是:确保INSTANCE==null
时,INSTANCE
没有正在创建实例。这句话可能有点儿绕,为什么二者不是等价的呢?INSTANCE
为例): INSTANCE
这个引用赋值为刚刚开辟的内存的首地址null
就意味着它没有在创建实例。但是,JVM会优化重调上面的三个过程,将第三步放在第二步之前,导致没有volatile
修饰的double-check
方法无法确保单例。volatile
修饰之后,就应该万事大吉了。可惜,事实并非如此。在Java 5之前的几个版本中,JVM的内存模型是存在缺陷的,使用了volatile
关键字也不能肯定地禁止优化调序的发生。
public class Singleton{
private Singleton(){}
private static final Singleton INSTANCE=new Singleton();
public static Singleton getInstance(){
return INSTANCE;
}
}
public class Singleton{
private Singleton(){}
private static final Singleton INSTANCE;
static{
INSTANCE = new Singleton();
}
public static Singleton getInstance(){
return INSTANCE;
}
}
beforefieldinit
修饰还是被pricise
修饰。被前者修饰,即使类的某个方法被调用了,但是如果没有使用到INSTANCE
字段,那么它就不会被初始化,实现了full lazy instantiation
。而后者的方式则与Java类似。(拓展阅读:C# in depth)full lazy instantiation
,还得依靠静态内部类(见下面的例子)。
public class Singleton{
private static class SingletonHolder{
private static Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
private Singleton(){}
}
之前第一版中作者最推崇的模式就是本例中的静态内部类,实现了完全的lazy instantiation
。
//Singleton with public final field
public class Elvis{
public static final Elvis INSTACE = new Elvis();
private Elvis(){ ... }
public void leaveTheBuilding(){ ... }
}
//Sigleton with static factory
public class Elvis{
private static final Elvis INSTACE = new Elvis();
private Elvis(){ ... }
public static getInstace(){return INSTANCE;}
public void leaveTheBuilding(){ ... }
}
AccessibleObject.setAccessible
修改单例的访问属性,从而生成新的实例。为了解决这个问题,可以设置私有构造方法被二次调用时抛出一个异常。
public enum Elvis{
INSTANCE;
public void leaveTheBuilding(){ ... }
}
Serializable
接口时,都必须用transient
关键字修饰所有单例类的字段。并且重写readResovle
方法。来确保进行了序列化之后,再进行反序列化之后不会产生新的实例(即使构造方法是私有的,如果没有进行任何操作,反序列化仍可以生成新的实例)。
public class Elvis implement Serializable{
public static final Elvis INSTACE = new Elvis();
private transient int ID;
private Elvis(){ ... }
public void leaveTheBuilding(){ ... }
@Override
public Object readResolve(){
return INSTANCE;
}
}
transient
修饰,都不会被当作序列化的一部分,在反序列化时只去获取该静态字段的当前值。Serializable
接口都被嫌弃引起频繁的GC操作,安卓官方还推出了Parcelable
这个代替的序列化接口。