`
sharajava
  • 浏览: 65747 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

单例到底是怎么回事

    博客分类:
  • Java
阅读更多

主要参考: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;
	}
}
 

 

参考的文章中还有关于“注册”的模式实现的讨论,个人感觉很复杂但用处不大,这里就不细说了。

 

总之,一个看似简单的单例模式就包含了这么多值得思考的东西,感悟到做技术真的需要非常严谨啊!稀里糊涂,自以为是可不行哦。

 

 

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics