关于Java单例
线程安全、延迟加载、序列化反序列化反射安全
五种单例实现。
参考资料:
http://blog.csdn.net/haoel/article/details/4028232
public class SingletonTest implements Runnable { static SingletonClass1 instance = null; public static void main(String args[]) { // SingletonClass0 instance = new SingletonClass0();// The constructor // SingletonClass0() is not visible for (int i = 0; i < 15; i++) { new Thread(new SingletonTest()).start(); } // System.out.println(instance.getClass()); } @Override public void run() { // TODO Auto-generated method stub SingletonClass6.getInstance(); // System.out.println(SingletonClass7.instance.hashCode()); } } class SingletonClass0 { private static SingletonClass0 instance = null; private SingletonClass0() { System.out.println("constructor run"); } public static SingletonClass0 getInstance() {// wrong 单线才单线程下没问题,多线程下仍会有多个实例 if (instance == null) { instance = new SingletonClass0(); System.out.println(instance.hashCode()); } return instance; } } class SingletonClass1 { private static SingletonClass1 instance = null; private SingletonClass1() { System.out.println("constructor run"); } public static SingletonClass1 getInstance() {// wrong 多线程下仍会有多个实例,与上一种 // 区别在于过个线程的new过程被同步了 if (instance == null) { synchronized (SingletonClass1.class) { instance = new SingletonClass1(); System.out.println(instance.hashCode()); } } return instance; } } class SingletonClass2 { private static SingletonClass2 instance = null; private SingletonClass2() { System.out.println("constructor run"); } public static SingletonClass2 getInstance() { synchronized (SingletonClass2.class) {// right。只会有一个线程new实例,但阻碍了后续线程读实例 if (instance == null) { instance = new SingletonClass2(); System.out.println(instance.hashCode()); } } return instance; } } class SingletonClass3 { private static SingletonClass3 instance = null; private SingletonClass3() { System.out.println("constructor run"); } public static SingletonClass3 getInstance() { synchronized (SingletonClass3.class) {// right // 只会有一个线程new实例,不会影响后续线程读实例,但instance=new // SingletonClass3()在JVM内不是原子操作,内部的几个步骤可能乱序,从而出错 if (instance == null) { synchronized (SingletonClass1.class) { if (instance == null) { instance = new SingletonClass3(); System.out.println(instance.hashCode()); } } } } return instance; } } class SingletonClass4 { private static volatile SingletonClass4 instance = null;// right // 只有一个实例,volatite保证了在JVM内部new // instance的几个操作禁止指令重排序优化 private SingletonClass4() { System.out.println("constructor run"); } public static SingletonClass4 getInstance() { synchronized (SingletonClass4.class) { if (instance == null) { synchronized (SingletonClass1.class) { if (instance == null) { instance = new SingletonClass4(); System.out.println(instance.hashCode()); } } } } return instance; } } class SingletonClass5 {// right,但是由类加载器在类加载时创建实例,我们无法控制实例创建的时机以干一些事(比如某个配置文件,或是某个被其它类创建的资源) public volatile static SingletonClass5 instance = new SingletonClass5(); private SingletonClass5() { System.out.println("constructor run"); }; public static SingletonClass5 getInstance() { System.out.println(instance.hashCode()); return instance; } } class SingletonClass6 {// 仍然使用JVM本身机制保证了线程安全问题;由于 SingletonHolder 是私有的,除了 // getInstance() // 之外没有办法访问它,因此它只有在getInstance()被调用时才会真正创建;同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖 // JDK 版本 private static class SingletonHolder { private static final SingletonClass6 INSTANCE = new SingletonClass6(); } private SingletonClass6() { System.out.println("constructor run"); } public static final SingletonClass6 getInstance() { System.out.println(SingletonHolder.INSTANCE.hashCode()); return SingletonHolder.INSTANCE; } } class SingletonClass7 { public SingletonClass7() { System.out.println("constructor run"); } } enum SingletonClass { instance; }
http://mp.weixin.qq.com/s/q7-GWt87S7uJ9NY-s1z4SA
需要参数:懒汉式1。(线程安全改进3、需要才new改进4)
不需要参数:饿汉式2。(本身线程安全、需要才new改进5)
上述几种实现方式都存在反射、序列化、反序列化使得单例被破坏的问题,可能成为漏洞被利用,虽可采用手段加以解决(解决方式见下)但不简单高效(若单例类维持了其他对象的状态时还需要使他们成为transient的对象,此时就更复杂了) -> 使用枚举6。
反序列化时单例被破坏的解决方法:readResolve
public class Singleton implements java.io.Serializable { public static Singleton INSTANCE = new Singleton(); protected Singleton() { } //反序列时直接返回当前INSTANCE private Object readResolve() { return INSTANCE; } }
反射时单例被破坏的解决方法:修改构造器,让它在创建第二个实例的时候抛异常
public static Singleton INSTANCE = new Singleton(); private static volatile boolean flag = true; private Singleton(){ if(flag){ flag = false; }else{ throw new RuntimeException("The instance already exists !"); } }
线程不安全方案1 -> 线程安全方案3(synchronize) -> 线程安全方4(双锁检测,from jdk1.5)
-> 线程安全方案2(立即new静态实例)-> 线程安全方案5(需要才new静态实例)
-> 线程安全方案6(枚举)
立即new对象的方案(方案2)的缺点之一:实例创建时可能需要进行连服务端等操作(比如ObjectStorageClient),比较耗时,因此尽可能在需要用到时才创建。
1、
2、
3、
4、双_锁检测(DCL),JDK1.5之前volatile不能完全避免指令重排序优化,直到1.5才修复。因此JDK1.5之前Java中无法安全地使用DCL来实现单例模式。
5、
6、
枚举类实际上就是一个继承Enum类的类。
使用枚举单例的写法,我们完全不用考虑序列化和反射的问题。枚举序列化是由jvm保证的,每一个枚举类型和定义的枚举变量在JVM中都是唯一的,在枚举类型的序列化和反序列化上,Java做了特殊的规定:在序列化时Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象。同时,编译器是不允许任何对这种序列化机制的定制的并禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法,从而保证了枚举实例的唯一性。
在单例中,枚举也不是万能的。在android开发中,内存优化是个大块头,而使用枚举时占用的内存常常是静态变量的两倍还多,因此android官方在内存优化方面给出的建议是尽量避免在android中使用enum。
更多关于枚举可以见:Java小计-枚举