@XingdingCAO
2017-11-24T11:06:49.000000Z
字数 4160
阅读 2409
Java singleton constructor
singleton就是一个仅仅实例化一次的类(在任何时期,内存中都应该仅存在一个实例),称做:单例。单例通常代表了一个在本质上数量保持唯一的系统组件,如:窗口管理器、文件系统。
演进部分引自:http://wuchong.me/blog/2014/08/28/how-to-correctly-write-singleton-pattern/
先不去看《Effective Java》中介绍的单例模式,先看一下单例模式的演进过程。
//DON'T DO THISpublic 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 fieldpublic class Elvis{public static final Elvis INSTACE = new Elvis();private Elvis(){ ... }public void leaveTheBuilding(){ ... }}
//Sigleton with static factorypublic 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(){ ... }@Overridepublic Object readResolve(){return INSTANCE;}}
transient修饰,都不会被当作序列化的一部分,在反序列化时只去获取该静态字段的当前值。Serializable接口都被嫌弃引起频繁的GC操作,安卓官方还推出了Parcelable这个代替的序列化接口。