主要参考:http://www.javaworld.com/javaworld/jw-04-2003/jw-0425-designpatterns.html
及相应的转帖译文:http://blog.csdn.net/songylwq/article/details/6058771
单例是设计模式(时间长都有点忽略这个概念了,呵呵),也许你会说他是最“简单”的设计模式。
某种程度上是这样的,这个设计模式理解起来不困难。但是,在实现层面上,如果细研究,还是有很多值得思考和注意的地方的。
写个单例,很多人肯定信手拈来:
public class ClassicSingleton { private static ClassicSingleton instance = null; protected ClassicSingleton() { // Exists only to defeat instantiation. } public static ClassicSingleton getInstance() { if (instance == null) { instance = new ClassicSingleton(); } return instance; } }
看起来不错,还挺“聪明”,“懒”加载的模式。
但是,经验多一些的,很快就发现,有漏洞。多线程环境下可能会被两个线程分别创建出两个实例(不单例)了。
解决线程安全问题,方法也很简单,同步关键字:
public static synchronized ClassicSingleton getInstance() { if (instance == null) { instance = new ClassicSingleton(); } return instance; }
这样安全是安全了,但效率很低。线程同步的开销比普通的调用要大很多,而对于这个单例的getInstance()方法,实际上只需要在第一次调用时同步即可,唯一的单例实例创建好之后的调用,都大可不必须synchronized。
很自然地,我们想到,不同步整个方法,同步创建对象的代码块吧:
public static /*synchronized*/ ClassicSingleton getInstance() { if (instance == null) { synchronized (ClassicSingleton.class) { instance = new ClassicSingleton(); } } return instance; }
这样行吗?很可惜,不成啊!这样的实现方式还是非线程安全的,道理跟不synchronized一样(这里不细解释了)。
继续补救,同步块里再判断,双保险,OK吗?
public static/* synchronized */ClassicSingleton getInstance() { if (instance == null) { synchronized (ClassicSingleton.class) { if (instance == null) {// double-check instance = new ClassicSingleton(); } } } return instance; }
对不起,还是不行。因为Java在给变量赋值前,会先“随便” 给个值,那一“瞬间”,变量是不空(null)的。也就是说,内层的check会在特定的瞬间失效。
所以……别在同步上费劲了,没有好的结果。
实际上,最简单也最有效的方法只是因为我们“误会”了,所以没有使用。
public class Singleton { public final static Singleton INSTANCE = new Singleton(); private Singleton() { // Exists only to defeat instantiation. } }
类的静态变量天然保证了线程安全,new的操作仅在类被加载时调用一次,可以说这是由类加载机制保证的。
你可能会说,这不是“懒”加载的。其实是误会,类的静态变量只有在首次访问时才进行初始化,也就是说,这个实现本身就是“懒”加载的。
这里顺便提到,一个问题:单例的范围是什么?
怎么回答?我觉得应该是一个类加载器(体系)的范围。可以想象,同一个应用服务器的两个不同的Web应用中,同一个类,肯定是有其各自的单例。如果希望这种更大范围也“单例”,那就需要在类加载器做文章了。
这种场景一般没有什么需要,这里就不深入讨论了。
多线程环境下的单例问题解决了,还有一点特别的,就是序列化。
如果希望实现单例的类实现了java.io.Serializable接口,就会存在被序列化机制破坏单例的情况。在另外一篇博文中有提到:http://sharajava.iteye.com/admin/blogs/1270722
解决方案就是实现readResolve()方法:
class Singleton implements java.io.Serializable { public final static Singleton INSTANCE = new Singleton(); private Singleton() { // Exists only to defeat instantiation. } private Object readResolve() { return INSTANCE; } }
参考的文章中还有关于“注册”的模式实现的讨论,个人感觉很复杂但用处不大,这里就不细说了。
总之,一个看似简单的单例模式就包含了这么多值得思考的东西,感悟到做技术真的需要非常严谨啊!稀里糊涂,自以为是可不行哦。