实现方式一览
第一种(懒汉,线程不安全)
public class Singleton { private static Singleton instance; private Singleton (){} public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
第二种(懒汉,线程安全)
public class Singleton { private static Singleton instance; private Singleton (){} public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
这种写法能够在多线程中很好的工作,而且看起来它也具备很好的lazy loading,但是,遗憾的是,效率很低,99%情况下不需要同步。
第三种(懒汉,双重校验锁)
public class Singleton { private volatile static Singleton singleton; private Singleton (){} public static Singleton getSingleton() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } }
在JDK1.5之后,双重检查锁定才能够正常达到单例效果。因为1.5之前的volatile关键字没有正确的实现其语义(存在乱序写入问题,即编译器重排优化,参考:Java单例模式中双重检查锁的问题)。
饿汉模式Ⅰ(静态属性直接初始化)
public class Singleton { private static Singleton instance = new Singleton(); private Singleton (){} public static Singleton getInstance() { return instance; } }
饿汉模式Ⅱ静态属性静态块中初始化)
public class Singleton { private static Singleton instance; static { instance = new Singleton(); } private Singleton (){} public static Singleton getInstance() { return instance; } }
4和5中的本质是一样的,基于classloder机制避免了多线程的同步问题。一般认为静态属性直接赋值和static块在类加载后就会执行,这是错误的认识,而是在类被主动使用时才会执行(初始化)。详见http://blog.csdn.net/berber78/article/details/46472789
静态内部类
public class Singleton { private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton (){} public static final Singleton getInstance() { return SingletonHolder.INSTANCE; } }
此模式与4和5保证线程安全的原理一样,只不过加了懒加载功能,因为私有静态内部类截断了其他触发初始化操作的条件,只剩下一个条件:访问SingletonHolder类的静态属性INSTANCE,也就是我们的单例对象。
枚举
public enum Singleton { INSTANCE; public void whateverMethod() { } }
“这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象”
上面是网上的人崇拜大神说的,其实他不明白其中的原理,上面的代码等价于:public class Singleton extends Enum<Singleton> { public static final Singleton[] values() { return (Color[])$VALUES.clone(); } public static Singleton valueOf(String name) { //return $VALUES[indexOfname];大概逻辑 } private Singleton(String s, int i) { super(s, i); } public static final Singleton INSTANCE; private static final Color $VALUES[]; static { INSTANCE = new Color("INSTANCE", INSTANCE); $VALUES = (new Color[] { INSTANCE}); } }
自习分析,方向跟4和5有异曲同工之妙。但是在您不想使用INSTANCE时千万不要调用valueOf和values方法,因为您的意图不是使用INSTANCE,而此时却加载了实例。请不要在枚举类里面写静态方法和其他静态属性,因为你一旦访问,就会创建实例,违背了懒加载的初衷。而enum被推崇的另外一个原因是反序列化不会创建新对象,因为Enum类里的三个方法:
/** * prevent default deserialization */ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { throw new InvalidObjectException("can't deserialize enum"); } private void readObjectNoData() throws ObjectStreamException { throw new InvalidObjectException("can't deserialize enum"); }
不是两个吗?你骗人!还有一个readResolve隐式实现了,readObject和readObjectNoData都直接抛出了异常,序列化机制明白是要阻止默认的反序列化方式,就会调用readResolve,readResolve返回一个匹配的对象就行了,我想java内部对于enum类型的这个方法的实现应该是直接返回INSTANCE吧!
Effective Java作者Josh Bloch不会没发现这些问题吧?他应该发现了,只不过大神都懒得解释,因为对于规范使用枚举的人,一般不会出现那些问题。Josh Bloch应该是从简洁、不易出错、高性能的角度认为这是最好的方式。但是这种方式比较诡异,感觉枚举被用坏了似的。
总结
方式1单线程可用,但是单线程的程序很少,不能实现线程安全不推荐使用
方式2多线程可用,但是性能不好,不是每次访问都需要加上锁
方式3多线程可用,java 1.5版本之后可用
方式4和5多线程可用,但是不会懒加载
方式6多线程可用,very good
方式7简洁诡异,但是需要注意那几个点