在实际开发过程中,常见使用Double Check Locking(DCL)实现延迟初始化的单例模式。在Java中,虽然这早已被证实是一种有害的编程习惯,但这并不能阻止它在程序员之间的传播。DCL存在线程安全隐患,不少Java编程书上有关于这个问题的介绍,网上也很多讨论。不过通常情况(并发量不大、安全性要求不高)下,它能够工作得很好,这大概就是它得以流传的原因吧。 DCL是一种反模式,典型的DCL如下: 01 | public class Singleton { |
02 | private static Singleton instance = null ; |
08 | public static Singleton getInstance() { |
09 | if (instance == null ) { |
10 | sychronized (Singleton. class ) { |
11 | if (instance == null ) { |
12 | instance = new Singleton(); |
按照网上人们对DCL的分析,线程安全问题在于instance = new Singleton();这行代码:第一个线程执行到此处时,可能是先分配对象的内存空间,在尚未初始化对象之前,已将这块空间的地址赋值给instance变量,之所以这样,是因为编译器会执行指令重排(statement reordering)的优化,使得实际执行的指令顺序并非按照语句的自然顺序,即乱序(out of order)执行。进而导致第二个线程在外层if条件处跳过,直接返回instance引用,使得对象处在不完整的状态,这被称为Unsafe Publication(见Java Concurrency in Pratice)。 曾经遭遇过一次由延迟初始化带来的问题,对DCL的危害才有了切身的体会。当时的代码大体如下: 01 | public class MemcachedService { |
02 | private static MemcachedService instance = null ; |
04 | private MemcachedService() { |
08 | public static MemcachedService getInstance() { |
09 | if (instance == null ) { |
10 | sychronized (MemcachedService. class ) { |
11 | instance = new MemcachedService(); |
17 | public Object getValue(String key) { |
为简明起见,省略了部分代码。 实际上,这段代码并非DCL,它比DCL的安全性更弱。它本来是希望延迟实例化对象,并通过sychronized块限制对象只实例化一次。而由于if判断和sychronized块之间不是原子操作,实例化可能会执行多次。再者,仍然是instance = new MemcachedService();这行代码,对MemcachedService实例的创建和对instance变量的写入操作可能会被重排,造成Unsafe Publication。 这段代码是用在一个web应用程序中,用来从cache中获取事先存储的数据。在测试环境中一切良好,一部署到线上环境,即发现getValue处无法获取cache值。开始怀疑是环境问题,后来在线上环境同一台机器另部署了一套相同的程序,一切正常。为这个问题几乎折腾了一整天,当最后定位到延迟初始化问题时,原因便显而易见:线程安全缺陷通常在高并发的情况下问题才会显露,测试环境由内部人员使用,访问量很小,也没有进行压力测试,所以问题没有呈现;而线上环境并发访问量很高,程序一上线,问题立马显露无疑。 既然DCL存在线程安全隐患,且一旦出问题又难以调试,所以人们建议永远不要在Java程序中使用DCL。实际上,在整个应用范围内的单例(特别是在服务端程序中),往往没必要采用延迟初始化。延迟初始化主要是为了让一些昂贵的操作,在真正需要时才被执行。对于为大量持续不断的客户请求提供服务的程序来说,对单例的访问总是急切的,所以延迟初始化没有什么实际意义。 上面的代码改写成这样(急切初始化)既简洁又安全,同时消除了同步的开销: 01 | public class MemcachedService { |
02 | private static final MemcachedService instance = new MemcachedService(); |
04 | private MemcachedService() { |
07 | } catch (Exception e) { |
12 | public static MemcachedService getInstance() { |
16 | public Object getValue(String key) { |
update @2011-07-12 感谢scala指正,上述代码有安全问题,通过反射就能创建多个实例。
=================== Effect Java 推荐延迟初始化单例模式 ================ 针对 liuchangit 称自己为 java geek,说明还是个会不断思考钻研的人 ,扣点小东西 呵呵。 (1) 既能延迟加载 又能规避多线程问题,以下只是个粗略的版本,少了private构造 还有很多安全问题 public class Singleton { private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } public static final Singleton getInstance() { return SingletonHolder.INSTANCE; } } (2)MemcachedService 这个类是有安全问题的,通过反射就能创建多个实例,如果有人想搞你的话,当然 这种概率很小,所以 针对geek 才会说2句 现在能说2句的真的太少了。。。。
|