JD.SYNC.DCL

当找到双重检查锁定(如下面的第一个示例所示)时,发生 JD.SYNC.DCL。

漏洞与风险

双重检查锁定被广泛引用,并作为在多线程环境中实施迟缓初始化的有效方法。遗憾的是,当在 J2SE 1.4(或较早版本)中实施时,它不能以独立于平台的方式可靠地运行。虽然双重检查锁定一词不能用于对于对象的引用,但其可用于 32 位原始值(如 int 或 float)。注意,其不能用于长值和双精度值,因为不能保证 64 位原始值的非同步读写是原子操作。

在某些情况下,由编译器生成的代码包含将未完成初始化的对象分配到语句前的字段以完成对象的初始化,这时不能使用双重检查锁定模式。因此,其他线程将能通过默认字段值查看对于对象的非 null 引用。即使编译器不重新排列这些语句,在多处理器系统中,当其他处理器中的线程发觉时,处理器或存储器系统也会重新排列这些语句。

缓解与预防

自 J2SE 5.0 起,该问题已修复。“volatile”关键字现在可确保多线程可以正确地处理单一实例。如果创建的是静态单一实例,解决方案是在单独的类中将单一实例定义为静态字段。Java 的语义可确保在引用字段前不会将其初始化,并可确保访问该字段的所有线程都能查看因初始化该字段而进行的所有写入操作。另外,也可以在不进行双重检查的情况下使用同步,但应注意,同步一个方法可能会大大降低性能(见下面的第二个示例)。有关详情,请参阅维基百科文章:双重检查锁定

示例 1

复制
      class MyClass {
         MyClass son;
         void doubleCheckedLocking() {
             if (son==null) {
                 synchronized (this) {
                     if (son==null) {
                         son = new MyClass();
                     }
                 }
             }
         }
     }

针对第 14 行报告 JD.SYNC.DCL:son(未达到相应目标的习语)的双重检查锁定。

示例 2

复制
     class MyClass {
         private MyClass son = null;
         public synchronized void doubleCheckedLocking() {
             if (son == null)
                 son = new MyClass();
         }
     }

方法已同步,无需双重检查。