真实 Java 应用程序中的单例模式和损坏的双重检查锁定

发布于 2024-08-25 16:23:48 字数 435 浏览 4 评论 0原文

我正在阅读文章 双重检查锁定和单例模式,关于双重检查锁定如何被破坏,以及 StackOverflow 上的一些相关问题。

我已经多次使用这种模式/习惯用法,没有任何问题。自从我一直在使用 Java 5 以来,我的第一个想法是 Java 5 内存模型已经纠正了这个问题。然而文章说:

本文涉及Java内存 针对 Java 进行修改之前的模型 5.0;有关内存排序的陈述可能不再正确。 然而, 双重检查锁定惯用法仍然是 在新的内存模型下崩溃了

这是一个真正的问题吗?如果是,在什么条件下?

I was reading the article Double-checked locking and the Singleton pattern, on how double checked locking is broken, and some related questions here on Stack Overflow.

I have used this pattern/idiom several times without any issues. Since I have been using Java 5, my first thought was that this has been rectified in Java 5 memory model. However the article says:

This article refers to the Java Memory
Model before it was revised for Java
5.0; statements about memory ordering may no longer be correct. However, the
double-checked locking idiom is still
broken under the new memory model
.

Is this a real problem, and, if so, under what conditions?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

花开浅夏 2024-09-01 16:23:48

说明关于双重检查锁定的单例示例,该示例看起来很聪明,但已被破坏

同步块的开始保证您看到最新的数据,但它不能保证重新排序,您不能期望获得一致的数据视图,除非您
也在同步块中。它不保证在同步部分中完成的变量修改对其他线程可见。只有进入同步块的线程才能保证看到更改。这就是双重检查锁定被破坏的原因 - 它在读者端不同步。 读取线程可能会看到单例不为空,但单例数据可能未完全初始化(可见)

另一方面,排序是由 volatile 提供的,它保证了排序,例如写入 volatile 单例静态字段可以保证对单例对象的写入将在写入 volatile 静态字段之前完成。它不会阻止创建两个对象的单例;这是由同步提供的。
类最终静态字段不需要是易失性的。在 Java 中,JVM 解决了这个问题。

更多信息可以在:

Illustrating example of a singleton with respect to double-checked locking that looks clever but is broken

The start of a synchronization block guarantees that you see the latest data, but it does not guarantee reordering, you cannot expect a consistent view of data unless you
are also in a synchronized block. It doesn't guarantee, that variables modifications done within synchronized section will be visible to other threads. Only the threads that enters the synchronized block is guaranteed to see the changes. This is the reason why double checked locking is broken - it is not synchronized on the reader's side. The reading thread may see, that the singleton is not null, but singleton data may not be fully initialized (visible).

On the other hand, ordering is provided by volatile which guarantees ordering, for instance write to volatile singleton static field guarantees that writes to the singleton object will be finished before the write to a volatile static field. It doesn't prevent creation singleton of two objects; this is provided by synchronize.
Class final static fields doesn't need to be volatile. In Java, the JVM takes care of this problem.

More can be found in:

度的依靠╰つ 2024-09-01 16:23:48

人们很难确定他们的应用程序实际上受到了双重检查锁定故障的影响。事实上,由于各种原因,许多使用此习惯用法的应用程序可能永远不会遇到该问题。

但是,这并不意味着您应该使用它。仅存在不可量化的失败概率这一事实就足以说服您不要使用双重检查锁定,特别是因为有安全的替代方案。

你只是很幸运。

It would be difficult for someone to be sure that their application had actually been hit by a double-checked lock failure. Indeed, many applications that used this idiom may never experience the problem for a variety of reasons.

However, that doesn't mean that you should use it. The mere fact there is a non-quantifiable probability of failure should be sufficient to persuade you not to use double-checked locking, especially since there are safe alternatives.

You've just been lucky.

孤千羽 2024-09-01 16:23:48

我们有一个应用程序使用了损坏的双重检查惯用法,并且它在很长一段时间内都运行良好 - 不,事实上,我从未遇到过这种惯用法的问题。当然,无论如何我都修复了它。

我想原因之一是线程可见性最终将在现实世界中实现。一旦达到,它就会留下来。所以是的,检测问题是否已经发生将非常困难。

我相信 StringhashCode() 实现部分依赖于这个事实...线程在看不到缓存时计算 hashCode,但最终它们开始看到。同时,重复计算意味着只是浪费了一些 CPU 时间,并且避免了内存效应的好处易失性语义胜过这种浪费的努力(至少这就是我猜他们以这种方式实现的原因)。有效使用的习惯用法是(实际的 String.hashCode() 实现):

/** Cache the hash code for the string */
private int hash; // Defaults to 0

public int hashCode() {
    int h = hash;
    if (h == 0) {
        int off = offset;
        char val[] = value;
        int len = count;

        for (int i = 0; i < len; i++) {
            h = 31*h + val[off++];
        }
        hash = h;
    }
    return h;
 }

显然,在使用它之前必须进行大量思考和衡量。

We had an application which used a broken double-check idiom, and it functioned perfectly for a very long time - no, in fact, I have never experienced problems with this idiom. Of course I fixed it regardless of that.

I guess one of the reasons of this is that thread visibility will eventually attained in the real world. And once attained, it stays. So yes, it will be very difficult to detect whether the problem has happened.

I believe the hashCode() implementation of String partially relies on this fact... Threads calculate hashCode while they don't see the cache, but eventually they start to see. Meanwhile, duplicate calculation means just some wasted CPU time, and the benefit of avoiding the memory effect of volatile semantics trumps this wasted effort (at least that's why they implemented it that way I guess). The idiom that has valid use is (actual String.hashCode() implementation):

/** Cache the hash code for the string */
private int hash; // Defaults to 0

public int hashCode() {
    int h = hash;
    if (h == 0) {
        int off = offset;
        char val[] = value;
        int len = count;

        for (int i = 0; i < len; i++) {
            h = 31*h + val[off++];
        }
        hash = h;
    }
    return h;
 }

Obviously, one has to think and measure a lot before using it.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文