单例模式
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
单例类只能有一个实例。
单例类必须自己创建自己的唯一实例。
单例类必须给所有其他对象提供这一实例。
单例模式的5种实现方式
懒汉式-线程不安全 懒得创建,你调用我的时候再创建
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package test.singleton;import java.io.Serializable;public class SingletonOne { private static SingletonOne instance = null ; private SingletonOne () { } public static SingletonOne getInstance () { if (instance == null ) { instance = new SingletonOne(); } return instance; } }
饿汉式-天生线程安全 早就创建好了,随时等着调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package test.singleton;public class SingletonTwo { private static SingletonTwo instance = new SingletonTwo(); private SingletonTwo () { } public static SingletonTwo getInstance () { return instance; } }
双检锁/双重校验锁(DCL,即 double-checked locking) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package test.singleton;public class SingletonThree { private volatile static SingletonThree instance = null ; private SingletonThree () { } public static SingletonThree getInstance () { if (instance == null ) { synchronized (SingletonThree.class) { if (instance == null ) { instance = new SingletonThree(); } } } return instance; } }
需要加volatile关键字,否则会出现错误。问题的原因在于JVM指令重排优化的存在。在某个线程创建单例对象时,在构造方法被调用之前,就为该对象分配了内存空间并将对象的字段设置为默认值,此时就可以将分配的内存地址赋值给instance字段了。然而该对象可能还没有初始化(写完还未同步主存),若紧接着另外一个线程来调用getInstance,取到的就是状态不正确的对象,程序就会出错。
由于 synchronized 并不是对 instance实例进行加锁(因为现在还并没有实例),所以线程在执行完instance = new SingletonThree()修改 instance 的值后,应该将修改后的 instance立即写入主存(main memory),而不是暂时存在寄存器或者高速缓冲区(caches)中,以保证新的值对其它线程可见。
至此,我们要加volatile关键字,保证内存可见性。
登记式/静态内部类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package test.singleton;public class SingletonFour { private SingletonFour () { } private static class SingletonFourHolder { private static final SingletonFour INSTANCE = new SingletonFour(); } public static final SingletonFour getInstance () { return SingletonFourHolder.INSTANCE; } }
枚举 1 2 3 4 5 6 7 8 9 10 11 package test.singleton;public enum SingletonFive { INSTANCE; public void doSomeThing () { System.out.println("123321" ); } }
使用:
1 2 SingletonFive.INSTANCE.doSomeThing();
说明:
一般情况下,不建议使用第 1 种方式,建议使用第 2 种饿汉方式。只有在要明确实现 lazy loading 效果时,才会使用第 4 种登记方式。如果涉及到反序列化创建对象时,可以尝试使用第 5 种枚举方式。如果有其他特殊的需求,可以考虑使用第 3 种双检锁方式。
破坏单例模式的三种方式
反射,克隆,序列化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package test.singleton;import java.io.*;import java.lang.reflect.Constructor;public class SingletonDemo { public static void main (String[] args) throws Exception { System.out.println("---------------序列化------------------" ); SingletonOne singleton = SingletonOne.getInstance(); ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(singleton); ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois =new ObjectInputStream(bis); SingletonOne serializeSingleton = (SingletonOne) ois.readObject(); System.out.println(singleton == serializeSingleton); System.out.println("---------------克隆------------------" ); SingletonOne cloneSingleton = (SingletonOne) singleton.clone(); System.out.println(singleton == cloneSingleton); System.out.println("---------------反射------------------" ); Constructor<SingletonOne> cons = SingletonOne.class.getDeclaredConstructor(); cons.setAccessible(true ); SingletonOne reflextSingleton = cons.newInstance(); System.out.println(singleton == reflextSingleton); } }
运行结果:
1 2 3 4 5 6 ---------------序列化------------------ false ---------------克隆------------------ false ---------------反射------------------ false
这三种方式的规避方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 package test.singleton;import java.io.Serializable;public class SingletonOne implements Serializable , Cloneable { private static volatile boolean isCreate = false ; private static SingletonOne instance = null ; private SingletonOne () { if (isCreate) { throw new RuntimeException("已创建过" ); } isCreate = true ; } public static SingletonOne getInstance () { if (instance == null ) { instance = new SingletonOne(); } return instance; } @Override protected Object clone () throws CloneNotSupportedException { return instance; } private Object readResolve () { return instance; } }
运行结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 ---------------序列化------------------ true ---------------克隆------------------ true ---------------反射------------------ Exception in thread "main" java.lang.reflect.InvocationTargetException at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62 ) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45 ) at java.lang.reflect.Constructor.newInstance(Constructor.java:423 ) at test.singleton.SingletonDemo.main(SingletonDemo.java:31 ) Caused by: java.lang.RuntimeException: 已创建过 at test.singleton.SingletonOne.<init>(SingletonOne.java:16 ) ... 5 more
这里我们简单说一下 为什么防止序列化要添加readResolve(),返回Object对象
一般来说, 一个类实现了 Serializable接口, 我们就可以把它往内存地写再从内存里读出而”组装”成一个跟原来一模一样的对象.
不过当序列化遇到单例时,这里边就有了个问题: 从内存读出而组装的对象破坏了单例的规则. 单例是要求一个JVM中只有一个类对象的, 而现在通过反序列化,一个新的对象克隆了出来,与以前序列化的对象不能equlas。
如果被反序列化的对象的类存在readResolve这个方法,他会调用这个方法来返回一个“array”,然后浅拷贝一份,作为返回值,并且无视掉反序列化的值,即使那个字节码已经被解析。