【问题标题】:Name for this pattern? (Answer: lazy initialization with double-checked locking)这种模式的名称? (答案:带有双重检查锁定的延迟初始化)
【发布时间】:2011-05-15 20:35:21
【问题描述】:

考虑以下代码:

public class Foo
{
    private static object _lock = new object();

    public void NameDoesNotMatter()
    {
        if( SomeDataDoesNotExist() )
        {
            lock(_lock)
            {
                if( SomeDataDoesNotExist() )
                {
                    CreateSomeData();
                }
                else
                {
                    // someone else also noticed the lack of data.  We
                    // both contended for the lock.  The other guy won
                    // and created the data, so we no longer need to.
                    // But once he got out of the lock, we got in.
                    // There's nothing left to do.
                }
            }
        }
    }

    private bool SomeDataDoesNotExist()
    {
        // Note - this method must be thread-safe.
        throw new NotImplementedException();
    }

    private bool CreateSomeData()
    {
        // Note - This shouldn't need to be thread-safe
        throw new NotImplementedException();
    }
}

首先,我需要说明一些假设:

  1. 有一个很好的理由,我不能只在应用启动后执行此操作。也许数据还不可用,等等。

  2. Foo 可以从两个或多个线程同时实例化和使用。我希望其中一个最终创建一些数据(但不是两个),然后我将允许两者访问相同的数据(忽略访问数据的线程安全)

  3. SomeDataDoesNotExist() 的开销并不大。

现在,这不一定限于某些数据创建情况,但这是我能想到的一个例子。

我对识别为模式特别感兴趣的部分是检查 -> 锁定 -> 检查。有几次,我不得不向那些乍一看并没有理解算法但后来理解它的开发人员解释这种模式。

无论如何,其他人也必须这样做。这是标准化的模式吗?它叫什么?

【问题讨论】:

  • 对我来说,使用同步 lazy-loadedsingleton pattern 似乎是一种奇怪的方式。
  • 您的意思是可能有更好但功能等效的方法来执行此操作。你具体是怎么想的?
  • 我说的很奇怪,因为它将多种模式组合成一种方法。通常我希望以不同的方法看到它们。即,延迟加载的单例(数据)将位于单独的方法/属性中。
  • 第 2 点有问题。访问数据时需要某种“线程安全”。没有它,您无法确保在访问数据之前已完全创建数据。
  • 我同意,伊什塔尔。我只是想从概念上分离这个问题。但是您绝对是对的,检测数据似乎存在但不完整的场景很重要。

标签: c# java design-patterns


【解决方案1】:

虽然我可以看出您可能认为这看起来像是双重检查锁定,但实际上它看起来是危险的损坏和不正确的双重检查锁定。如果没有 SomeDataDoesNotExist 和 CreateSomeData 的实际实现来批评,我们无法保证这个东西在每个处理器上实际上都是线程安全的。

有关双重检查锁定如何出错的分析示例,请查看这个损坏且不正确的双重检查锁定版本:

C# manual lock/unlock

我的建议:不要在没有令人信服的理由和内存模型专家的代码审查的情况下使用任何低锁技术;你可能会弄错。大多数人都这样做。

特别是,不要使用双重检查锁定,除非您可以准确描述处理器可以代表您执行的内存访问重新排序,并提供一个令人信服的论点,即您的解决方案是正确的任何可能的内存访问重新排序。当您稍微偏离已知正确的实现时,您需要从头开始分析。您不能仅仅因为双重检查锁定的一种实现是正确的,就认为它们都是正确的;几乎没有一个是正确的。

【讨论】:

  • 说得很好,埃里克。在我启发这一点的真实代码中, SomeDataDoesNotExist() 是完全线程安全的(我将避免提及原因,因为这可能会让我们陷入困境)。如果不是,那将是一个大问题。该链接很有帮助。我相信我很安全,但我会重新阅读该链接几次以确保安全。
  • 我刚刚修改了我原来的帖子,在最后两个方法中添加了 cmets。
  • 我还将考虑使用一些更高级别的结构更简单地完成我对低级别锁所做的事情的可能性。
  • @Matthew:SomeDataDoesNotExist 是否线程安全无关紧要;我的意思是,是的,它必须是线程安全的,但还需要更多。 整个模式需要是线程安全的,在一个不同地址的读取可以任意重新排序的世界中,如从多个处理器观察到的那样。这比单纯的线程安全要求更深、更复杂!
  • @Pure.Krome:完全普通的锁定。 低锁定技术是危险的。 避免它们。拿锁;它可能已经足够快了。
【解决方案2】:

带有双重检查锁定的延迟初始化?

【讨论】:

【解决方案3】:

我对识别为模式特别感兴趣的部分是检查 -> 锁定 -> 检查。

那叫double-checked locking

请注意,在较旧的 Java 版本(Java 5 之前)中,由于 Java 的内存模型是如何定义的,它并不安全。在 Java 5 和更新的版本中,对 Java 内存模型的规范进行了更改,使其现在是安全的。

【讨论】:

  • 好了,这就是我所追求的正是。我只是没有它的词典。我正在尝试接受这个答案,但我是新手,请稍等。
  • 当网站实际上允许我投票给人们时,我会投票给你的答案。感谢您的宝贵时间。
【解决方案4】:

想到这种类型的唯一名称是“故障”。这个名字用在 iOS Core-Data 框架中,效果类似。

基本上,您的方法 NameDoesNotMatter 是一个错误,每当有人调用它时,它都会导致对象被填充或初始化。

有关如何使用此设计模式的更多详细信息,请参阅http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CoreData/Articles/cdFaultingUniquing.html

【讨论】:

  • 有趣。在 Objective-C 中,这种错误本质上是线程安全的吗?或者我们可以说我的错误是线程安全的?
  • 由于触发故障而导致的故障和新对象的创建是线程安全的 AFAIK,但仅在“上下文”内。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-01
  • 1970-01-01
  • 2020-05-10
相关资源
最近更新 更多