枚举实现的单例模式

常见单例

在用枚举实现单例模式之前,先用常见的方式来实现这些单例模式

/**   
 * 实现单例访问Kerrigan的第一次尝试   
 */    
public class SingletonKerriganA {     
      
    /**   
     * 单例对象实例   
     */    
    private static SingletonKerriganA instance = null;     
      
    public static SingletonKerriganA getInstance() {     
        if (instance == null) {                              //line A     
            instance = new SingletonKerriganA();          //line B     
        }     
        return instance;     
    }     
}     

这种实现方式存在一个严重的问题,就是多线程问题,假设场景

两个线程并发调用SingletonKerriganA.getInstance(),假设线程一先判断完instance是否为null,既代码中的line A进入到line B的位置。刚刚判断完毕后,JVM将CPU资源切换给线程二,由于线程一还没执行line B,所以instance仍然是空的,因此线程二执行了new SignletonKerriganA()操作,这时会创建两个对象

于是我们对上述代码进行了改进:

/**   
 * 实现单例访问Kerrigan的第二次尝试   
 */    
public class SingletonKerriganB {     
      
    /**   
     * 单例对象实例   
     */    
    private static SingletonKerriganB instance = null;     
      
    public synchronized static SingletonKerriganB getInstance() {     
        if (instance == null) {     
            instance = new SingletonKerriganB();     
        }     
        return instance;     
    }     
}  

在第一个的代码基础之上,很容易发现,我们给这个方法添加了内置锁synchronized。

读者读到这,可能觉得已经差不多了,保证了单例,保证了线程安全,但是仔细想想,这段代码仍然存在不少问题,当有大量线程访问时,存在性能问题,串行化执行的,容易形成阻塞,想一想:我们其实只需要在第一次创建的时候给代码加锁:

于是代码又成了这样

    
    /**   
     * 单例对象实例   
     */    
    private static SingletonKerriganD instance = null;     
      
    public static SingletonKerriganD getInstance() {     
        if (instance == null) {     
            synchronized (SingletonKerriganD.class) {     
                if (instance == null) {     
                    instance = new SingletonKerriganD();     
                }     
            }     
        }     
        return instance;     
    }     
}    

但是这样写也并非完美的:

假设线程一执行到instance = new SingletonKerriganD()这句,这里看起来是一句话,但实际上它并不是一个原子操作(原子操作的意思就是这条语句要么就被执行完,要么就没有被执行过,不能出现执行了一半这种情形)。事实上高级语言里面非原子操作有很多,我们只要看看这句话被编译后在JVM执行的对应汇编代码就发现,这句话被编译成8条汇编指令,大致做了3件事情: 
 
1.给Kerrigan的实例分配内存。 
 
2.初始化Kerrigan的构造器 
 
3.将instance对象指向分配的内存空间(注意到这步instance就非null了)
于是再想,既然我们创建存在问题,倒不如,把创建对象的事直接交给jvm来做,更可靠,在类加载阶段,就创建好对象。
/**   
 * 实现单例访问Kerrigan的第六次尝试   
 */    
public class SingletonKerriganF {     
      
    private static class SingletonHolder {     
        /**   
         * 单例对象实例   
         */    
        static final SingletonKerriganF INSTANCE = new SingletonKerriganF();     
    }     
      
    public static SingletonKerriganF getInstance() {     
        return SingletonHolder.INSTANCE;     
    }     
}    

到这里先告一段落:

我们来看以下问题:

单例实现序列化接口

public class SingletonKerrigan implements Serializable {     
      
    private static class SingletonHolder {     
        /**   
         * 单例对象实例   
         */    
        static final SingletonKerrigan INSTANCE = new SingletonKerrigan();     
    }     
      
    public static SingletonKerrigan getInstance() {     
        return SingletonHolder.INSTANCE;     
    }     
      
    /**   
     * private的构造函数用于避免外界直接使用new来实例化对象   
     */    
    private SingletonKerrigan() {     
    }     
      
    /**   
     * readResolve方法应对单例对象被序列化时候   
     */    
    private Object readResolve() {     
        return getInstance();     
    }     
}    
public class Test01 {
    
    public static void main(String[] args) throws Exception{
       
       Instance i1 = Instance.getInstance();
       ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("b.txt"));
       oos.writeObject(i1);
       oos.flush();
       
       ObjectInputStream ois = new ObjectInputStream(new FileInputStream("b.txt"));
       Instance instance = (Instance) ois.readObject();
       System.out.println(instance == i1);
       ois.close();
       
    }
}
控制台打印的结果是flase

可以看出问题了,在实现序列化和反序列化时,并不能保证对象的唯一性:

这时需要在单例类中提供 这个方法:

 private Object readResolve() {     
        return getInstance();     
    }  

写到这儿:我们是不是对实现单例模式感到绝望呢?

 

别灰心:《Effective Java》书中,作者为我们提供了一种实现单例模式的最好的方式

public class SingletonKerrigan {     
      
    /**   
     * 单例对象实例   
     */    
    INSTANCE;     
              
    private SingletonKerrigan (){}
    
}     

 

posted on 2017-11-24 22:49  贝克田庄  阅读(350)  评论(0编辑  收藏  举报

导航