1

写在最前面

导师贪腐出逃美国,两年未归,可怜了我。拿了小米和美团的offer,要被延期,offer失效,工作重新找。把准备过程纪录下来,共勉。

单例模式有几种经典写法,核心思想:就是将构造函数私有化,并且通过静态方法获取一个唯一的实例。

饿汉式

    public class Singleton{
        private Singleton(){}
        
        private static Singleton instance = new Singleton();
        
        public static Singleton getInstance(){
            return instance;
        }
    }

懒汉式

    public class Singleton{
        private Singleton(){}
        
        private static Singleton instance = null;
        
        public static Singleton getInstance(){
            instance = new Singleton();
            return instance;
        }
    }

双锁式

    public class Singleton{
        private Singleton(){
        }
        
        private volatile static Singleton instance = null;
        
        public static Singleton getInstance(){
            if(instance == null){
                synchonized(Singleton.class){
                    if(instance == null){
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }

关于volatile关键字
上面添加了volatile关键字,如果没有volatile关键字,在执行instance = new Singleton()时可能会出现问题,伪代码如下:

inst = allocat();   // 第一步:分配内存  
constructor(inst);  // 第二步:执行构造函数  
instance = inst;    // 第三步:赋值,将instance对象指向分配的内存空间(此时instance就不是null了)  

由于Java编译器允许处理器乱序执行,所以第二步和第三步的顺序无法保证。如果第三步先执行完毕、第二步未执行时,有另外的线程调用了instance,由于已经赋值,将判断不为null,拿去直接使用,但其实构造函数还未执行,成员变量等字段都未初始化,直接使用,就会报错。这就是DCL失效问题,而且很难复现。

对volatile变量的写操作,不允许和它之前的读写操作打乱顺序;对volatile变量的读操作,不允许和它之后的读写乱序。

当一个线程要使用共享内存中的volatile变量时,它会直接从主内存中读取,而不是使用自己本地内存中的副本。当一个线程对一个volatile变量进行写时,它会将这个共享变量值刷新到共享内存中。

本节参考 http://blog.csdn.net/hxpjava1...

枚举类

    public enum Singleton{
        instance;
    }
关于序列化、反序列化

一个enum常量(这里是INSTANCE)代表了一个enum的实例,enum类型只能有这些常量实例。标准保证enum常量(INSTANCE)不能被克隆,也不会因为反序列化产生不同的实例,想通过反射机制得到一个enum类型的实例也不行的。

避免序列化、反序列化时产生多个实例,有两种解决方法

  1. 抛出异常

    /** 
     * prevent default deserialization 
     */  
    private void readObject(ObjectInputStream in) throws IOException,  
       ClassNotFoundException {  
           throw new InvalidObjectException("can't deserialize enum");  
    }  
  2. 在readResolve()方法中返回实例

    ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;  
    
    ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;  

    以上两个方法可以理解为序列化和反序列化过程的入口和出口。writeReplace()返回的对象,就是要被序列化的对象,我们有机会在序列化前把这个对象给换成我们确定好的那个(如果不是“故意捣乱”,暂时没想到有什么用);而readResolve()方法就是在反序列化完成得到对象前,把这个对象给换成我们确定好的那个。

/** 
 * 反序列化时内存Hook这段代码 
 * @return 
 */  
private Object readResolve() {  
    return instance;  
}  

本节参考 http://837062099.iteye.com/bl...

静态内部类

内部类的机制来巧妙实现懒汉式单例模式的实现 :Lazy initialization holder class模式。

内部类分为对象级别类级别,类级内部类指的是,有static修饰的成员变量的内部类。如果没有static修饰的成员变量的内部类被称为对象级内部类。

类级内部类相当于其外部类的static成员,它的对象与外部类对象间不存在依赖关系,相互独立,因此可直接创建。而对象级内部类的实例,是必须绑定在外部对象实例上的。类级内部类只有在第一次被使用的时候才被会装载。采用类级内部类,在这个类级内部类里面去创建对象实例,能够让类装载的时候不去初始化对象。

当getInstance方法第一次被调用的时候,它第一次读取SingletonHolder.instance,内部类SingletonHolder类得到初始化;而这个类在装载并被初始化的时候,会初始化它的静态域,从而创建Singleton的实例,由于是静态的域,因此只会在虚拟机装载类的时候初始化一次,并由虚拟机来保证它的线程安全性。

这个模式的优势在于,getInstance方法并没有被同步,并且只是执行一个域的访问,因此延迟初始化并没有增加任何访问成本。

    public class Singleton{
        private Singleton(){}
        
        private static class SingletonHolder{
            private static final Singleton instance = new Singleton();
        }
        
        public static Singleton getInstance(){
            return SingletonHolder.instance;
        }
    }

本节参考 http://blog.csdn.net/hikvisio...

全文参考 http://blog.csdn.net/u0129753...


菟潞寺沙弥
303 声望55 粉丝