【问题标题】:Demonstrating problems with improperly published objects演示与不正确发布的对象有关的问题
【发布时间】:2011-12-21 03:10:15
【问题描述】:

JCIP 警告我们不要发布不正确的对象(请参阅here)。如果一个对象是可变的,JVM 可能会决定在其初始化完成之前发布该对象。

所以代码

class Holder {public int h = 0;public Holder(int _h) {h = _h;}}
a = new Holder(10);

可能会变得有效

a = new Holder();
// inlined constructor
a.h = 10;

那么当不同线程访问a.h时,假设它永远不会为0,它可能会失败

// Thread #1 executing
a = new Holder();
// Thread #2 preempts
if (a != null) System.out.println("result: "+(100/a.h)); // oops...

我试图证明这个问题。但是我写的代码没有证明这一点。我做的是一个持有人

static class Holder {
    int h = 0;
    int dummy = 0;
    public Holder(int h) {
        // if construction takes time, maybe I have better chance creating the bug...
        for (long i=0;i<1000*1000;i++) {
            dummy += i*(-1+(i%2*2));
        }
        this.h = h;
    }
    public int verifyNonZero() {
        return 1/h;
    }
}

然后我运行了 1000 个线程,将 new Holder() 发布到静态 holder 变量,其他 1000 个线程运行 holder.verifyNonZero()。见full gist

它对 Java 1.6 并没有真正的帮助,即使是 -server

【问题讨论】:

  • 也许您必须为 Java 打开一些优化标志。找他们?
  • @AbhinavSarkar 我认为-server 应该可以解决问题。知道我应该添加哪个附加标志吗?
  • 是的,我知道,但是您是否知道特定的相关标志(例如,增加永久空间对我没有帮助,会不会...)?

标签: java multithreading concurrency


【解决方案1】:

不能保证安全这一事实并不意味着在您的环境中一定会出现问题。

据我所知,没有证据表明这种行为可以在现代 JVM 上得到证明。但是,有证据表明一些较旧的 JVM 受到了影响:

还要注意,乱序执行不仅可能由 JVM 引起,也可能由 CPU 引起。但是,x86 CPU 的内存模型比较保守(Who ordered memory fences on an x86?),所以这种行为也不会是常用的 CPU 造成的。

所以,我认为您不太可能在典型的现代环境中演示这个问题。

【讨论】:

  • 我认为 JCIP 暗示这是一个在实践中可能发生的问题。是否有可以演示的通用 JVM?也许是 Android 或 OpenJDK?
  • @Elazar:这是一个“一次编写,到处运行”的原则——他们不能说“在这个特定的环境中它是安全的”,因为你的程序可以在不同的环境中运行,并且允许它们重新排序操作。然而,由于没有证据表明这个问题存在于现代环境中,也许没有办法证明它。
【解决方案2】:

你是如何进行测试的? [编辑:哦,你链接到代码!]

JMM 保证在给定线程中,您将无法看到乱序的事物——也就是说,如果同一个线程正在实例化 Holder 并对其进行检查,它将永远不会看到未初始化状态。

因此,您必须在一个线程中实例化一个 Holder,然后在另一个线程中查看它。您如何共享该实例?常用方法(volatile 静态、同步或线程安全集合等)在其实例化线程发布 Holder 和其他线程读取 Holder 之间建立起先发生关系。

如果您想要不安全的发布,您的主要选择是:

  • 将其放入共享但非线程安全的集合中。这对您的测试有点冒险,因为集合本身可能会引发异常
  • 放入非易失性静态
  • 将其放入数组中(而不是使用 AtomicReferenceArray)

即便如此,你还是要依靠时机来犯错。正如您所做的那样,仅使用静态来重现此类比赛,我并没有太多运气;我认为代码不够复杂,无法让 CPU 进行花哨的缓存。

IBM 有一个实验性的 javaagent,ConTest,它故意尝试利用并发错误。我在一个故意线程危险的队列上试了一下(我相信这是一个故意的死锁)。如果没有 ConTest,队列很少会失败;有了它,大约 30% 的时间会出现故障。我不确定 ConTest 在内存可见性竞赛中是好是坏。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多