枚举实现的单例模式
常见单例
在用枚举实现单例模式之前,先用常见的方式来实现这些单例模式
/** * 实现单例访问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 (){} }