3

我已经阅读了有关双重检查锁定修复如何从不工作的所有内容,并且我不喜欢延迟初始化,但是能够修复遗留代码会很好,而且这样的问题太诱人了,无法尝试解决。

这是我的例子: private int timesSafelyGotten = 0; 私人助手 helper = null;

public getHelper()
{
    if (timesSafelyGotten < 1) {
        synchronized (this) {
            if (helper == null) {
                helper = new Helper();
            } else {
                timesSafelyGotten++;
            }
        }
    }
    return helper;
}

这样,同步代码必须运行一次以创建帮助程序,并且在第一次获取它时运行一次,因此理论上 timesSafelyGotten 不能递增,直到创建帮助程序的同步代码释放锁并且帮助程序必须完成初始化。

我认为没有问题,但它是如此简单,看起来好得令人难以置信,你怎么看?

迦勒·詹姆斯·德莱尔

4

3 回答 3

4

如果没有内存屏障(synchronizedvolatile或等效的 from java.util.concurrent),线程可能会看到另一个线程的操作以不同于它们在源代码中出现的顺序发生。

由于在读取 时没有内存屏障timesSafelyGotten,因此它可能会出现在另一个线程中,该线程在分配之前timesSafelyGotten会增加。这将导致从该方法返回。 helpernull

在实践中,这可能在您的单元测试期间适用于许多架构。但这是不正确的,最终会在某个地方失败。

双重检查锁定现在确实有效,但正确实施很棘手,而且相当昂贵。有一些惰性初始化模式不那么脆弱,更具可读性,并且不需要任何奇异的东西。

于 2009-12-10T06:41:29.467 回答
2

如果您使用的是 JDK5+,请使用 java.util.concurrent,在您的情况下可能是AtomicInteger

这些实用程序是专门提供的,因为没有人能够充分理解低级线程同步原语以使它们正常工作。

于 2009-12-10T05:28:54.557 回答
1

这不好。您可以获得 timeSafelyGotten > 1。示例:

  1. Thread1 检查是否成功并在同步线上停止
  2. Thread2 检查是否成功并在同步代码上停止。
  3. Thread3 检查是否成功并在同步代码上停止。
  4. Thread1 落入同步块,创建助手并离开该块。
  5. Thread2 落入同步块,增加 timeSafelyGotten 并离开该块。
  6. Thread3 落入同步块,增加 timeSafelyGotten 并离开该块。

所以 timeSafelyGotten = 2。

您应该再添加一项检查:

if (helper == null) {
    helper = new Helper();
} else if (timesSafelyGotten < 1) {
    timesSafelyGotten++;
}

或将同步上移:

synchronized(this) {
   if (timeSafelyGotten < 1) {
       ...
   }
}

第一种方法更好,因为它不会每次都使用同步。

还有一个提示:不要使用 synchronize( this ),因为有人也可以使用您的对象进行同步。使用特殊的私有对象进行内部同步:

classs MyClass {
    private Object syncRoot = new Object();

    ...
    synchronized(syncRoot) {
        ....
    }
}
于 2009-12-10T05:35:29.497 回答