Java双重检查锁定强制两次同步,可行吗?

3

我已经阅读了关于双重检查锁定修复永远不起作用的所有内容,也不喜欢延迟初始化,但是修复遗留代码并解决此类问题是很诱人的。

这是我的示例: 私有 int timesSafelyGotten = 0; 私有 Helper helper = null;

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

这样,同步代码必须在创建助手时运行一次,在第一次获取它时再运行一次,因此理论上timesSafelyGotten不能在创建助手的同步代码释放锁之后增加,而且助手必须完成初始化。

我认为没有问题,但它太简单了,似乎太美好了,你觉得呢?

Caleb James DeLisle


6
如果你阅读并理解了关于双重检查锁定为何失效的详细解释之一,那么你就会明白为什么你的代码也是有问题的。 - Stephen C
1
值得注意的是,使用Java 5和volatile关键字,您可以实现不间断的双重检查锁定。但在Java 4及更早版本中,您所说的确实是正确的。 - Kevin Montrose
2
值得注意的是,随着J5的出现以及无争用锁定性能的大幅提升,DCL几乎总是一种不必冒险进行的过早优化。 - Lawrence Dol
3个回答

4
没有内存屏障(synchronizedvolatile或来自java.util.concurrent的等效物),一个线程可能会看到另一个线程的操作以不同于它们在源代码中出现的顺序发生。
由于在读取timesSafelyGotten时没有内存屏障,因此另一个线程可能会看到timesSafelyGottenhelper被分配之前被递增。这将导致从方法返回null
在实践中,在您的单元测试期间,这可能在许多体系结构上都可以工作。但这是不正确的,并且最终会在某个地方失败。
双重检查锁定现在确实可以工作,但正确实现它很棘手并且相当昂贵。有一些用于惰性初始化的模式更加健壮,更易读,并且不需要任何奇特的东西。

你有最新版本Java中关于惰性初始化模式的好教程链接吗? - Thorbjørn Ravn Andersen
查看“Initialization on Demand Holder”习语:http://en.wikipedia.org/wiki/Initialization_on_demand_holder_idiom - erickson
我也喜欢IODH - 简单、惰性初始化、快速且不会出现同步问题。 - blackanchorage

2
如果您使用的是JDK5+,请使用java.util.concurrent,对于您的情况可能需要使用AtomicInteger
这些实用程序的提供是专门为了让没有人能够指望完全理解低级别的线程同步原语使其正常工作。

1

这不太好。你可以获得 timeSafelyGotten > 1。例如:

  1. 线程1检查是否成功,并停在同步行上
  2. 线程2检查是否成功,并停在同步代码上。
  3. 线程3检查是否成功,并停在同步代码上。
  4. 线程1进入同步块,创建帮助器并离开此块。
  5. 线程2进入同步块,增加 timeSafelyGotten 并离开此块。
  6. 线程3进入同步块,增加 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) {
        ....
    }
}

谢谢您的快速回复,线程2和3在同步代码(第5步)中被阻塞,直到helper完成初始化,是吗?如果是这样,那么它就可以工作了,对吧?另外,我喜欢synchronized(syncRoot)的想法。 - Caleb James DeLisle
是的,它们被阻塞了,但它们在第一次检查后被阻塞,并在解除阻塞后进入同步块。我已经重新编写了示例以使其更清晰。 - Andrew Lygin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接