三、Item 3:使用私有构造方法或者枚举来实现单例
单例即为只初始化一次的类,我们可以一贯性的认为,单例代表系统唯一的系统组件,就像是window manager(窗口管理器)或者file system(文件系统)。
我们有两种方法实现单例。但是两种方法我们都需要在这个待实现的单例中提供一个私有的构造方法和一个共有静态的方法来处理这个唯一的实例。在其中的一个实现中,我么希望类的成员变量是final修饰的。
public class Elvis {
public static final Elvis INSTANCE = new Elvis();
private Elvis () { ... }
public void leaveTheBuilding () { ... }
}
在上面的sinleton中,构造方法只会在共有静态成员初始化的时候执行唯一的一次,我们之所以不使用“public”或者“protected”来修饰该类的构造方法是因为只有“private”修饰符才能保证(guarantees)一个独占的(monoelvistic)领域(universe):这样,事实上,只有一个Elvis的实例在内存中(当Elvis这个类被实例化的时候)–不可能多或者少,只此唯一。
第二种创建singleton的方法就是,创建一个共有静态方法:
public class Elvis {
private static final Elvis INSTANCE = new Elvis(); private Elvis () { ... }
public static Elvis getInstance () { return INSTANCE; }
public void leaveTheBuilding () { ... }
}
每次调用Elvis.getInstance(),只会返回一个唯一的实例。
共有成员方法最为主要的优点就是:方法本身的声明(getInstance)就已经申明了这个类是单例。所以,该方法永远返回一个实例。
为了能够以实用上述任何一种方法构造的单例序列化,仅仅使得该类实现serializable接口是不充分的,为了保证在序列化的同时,该类仍旧是单例的,你必须为所有的实例域声明为“transient”,并提供一个“readResolve”(该方法来自于serializable接口)方法。否则,每次方序列化一个实例的时候,一个新的实例将会创建。下面就是我们需要在之前的例子中添加的内容。
public class Elvis implements Serializable {
private Elvis (){}
/**
* 实用volatile的作用(只能在JDK 1.5+中进行使用):
* 1、该变量在线程中不会存在副本,直接从内存中取出
* 2、该关键字会禁止指令冲排序优化
*/
/**
* 之所以要使用volatile关键字的原因
* 在instance=new Elvis();这个进行实例化的时候,JVM会做出下面的几件事情
* 1、在堆内存中查找该类是否曾经实例化过,如果没有,需要通过类加载器进行类的加载;
* 2、为实例分配内存空间;
* 3、调用类的构造方法进行实例化;
* 4、将实例化的对象指向分配的内存空间。
*
* 只要的两件事情即为:3和4会在JVM执行指令重拍优化的时候将指令执行的顺序进行交换,这样,如果
* 过线程在运行的时候后,多于一个线程中需要持有Elvis这个实例的对象,但是,
* JVM执行的顺序是4->3,那么,虽然该实例已经存在分配的内存空间,但是该类
* 还未进行实例化,会导致NullPointException.
*/
private volatile static Elvis instance = null ;
public static Elvis getInstance (){
if (instance==null ){
synchronized (Elvis.class){
if (instance==null ){
instance=new Elvis();
}
}
}
return instance;
}
protected Object readResolve ()
{
return instance;
}
}
在JDK 1.5之后,有第三中方法来实现单例,我们同过enum(枚举)来进行实现
public enum Elvis2 {
INSTANCE;
}
该种方法能够避免绝大多数的不良情况的发生,并且,这其实是实现单例最好 的方法