枚举实现的单例模式
枚举实现的单例模式
常见单例
在用枚举实现单例模式之前,先用常见的方式来实现这些单例模式
/** * 实现单例访问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; } }
但是这样写也并非完美的:
/** * 实现单例访问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 (){} }