为什么Netbeans抱怨同步调用被包含条件?

Cod*_*y S 2 java synchronization netbeans locking synchronized

说我有一些像这样的代码:

private static Thing t = null;
private static final Object lock = new Object();   

public static void foo() {
    if(t == null) { //Netbeans warning on this line.
        synchronized(lock) {
            if(t == null) {
                t = new Thing();
            }
        }
    }
    ...Do stuff with t...
}
Run Code Online (Sandbox Code Playgroud)

现在,foo托管在服务器上,可能会被许多用户同时调用.函数foo的目的是初始化t,如果它还没有,否则,使用初始化的实例来做任何需要完成的事情.

但是,Netbeans在这样的代码上给了我一个警告:

Remove the outer conditional statement

使用工具提示:

Double-checked locking

双重检查锁定的目的是避免等待人们释放锁定的瓶颈.如果只检查t是否为null,那么每个人都必须等待机会查看t是否为null,并且每个人-1都会有效地等待(因为t不会为null) .因此外在条件.

有了它,比如说,通过一些奇迹,5个人同时将t == null解析为true.一个用户将获得同步锁,看到t == null仍然是真的,并初始化它.然后,该用户将释放锁定,下一个用户将锁定它,并几乎立即释放它(因为t!= null),这将继续,直到5个用户通过它.一直以来,其他用户连接跳过同步锁,作为第一个用户做了t!= null.

那么,为什么Netbeans抱怨?这似乎是做AFAIK的最好方法.

思考?

Jon*_*eet 5

这是抱怨,因为书面说,它是不安全的 - 或至少可能是.

因为t不是volatile,读者线程有可能读取仍在构造t whileThing的新值.这将是一个糟糕的举动 - 使用部分构造的对象是可怕的.

在Java 5的新内存模型之前,它甚至不会被标记为字段的线程安全volatile.使用新的内存模型在某些情况下是安全的,甚至可能没有volatile,但我个人无论如何都会避免它:

  • 在大多数应用程序中,只是在每次调用中进行同步都不太可能是一个重要的瓶颈
  • 通常初始化作为静态初始化程序的一部分就足够了:

    private static Thing t = new Thing();
    
    Run Code Online (Sandbox Code Playgroud)
  • 对于其他情况,有嵌套类的替代技巧仍然使用静态初始化器的懒惰,但即使访问其他成员,所有这些单个属性也是惰性的.

  • 对于直单单元模式,请考虑使用枚举.

对于很多对DCL的老破碎的更多信息,请阅读"双检锁坏了"宣言.(关于JDK 5的当前情况,请参阅最后一节 - 但请考虑一下您是否真的希望代码的维护者考虑所有这些.)