代码之家  ›  专栏  ›  技术社区  ›  Finbarr

这在技术上是线程安全的吗?

  •  8
  • Finbarr  · 技术社区  · 14 年前

    是,私有成员变量 bar 应该是 final 正确的?但实际上,在本例中,只需读取 int . 那么从技术上说这是线程安全的吗?

    class Foo {
        private int bar;
        public Foo(int bar) {
            this.bar = bar;
        }
        public int getBar() {
            return bar;
        }
    }
    

    //假设无限个线程重复调用 getBar 在同一个实例上 Foo .

    编辑:

    假设这是 类;任何引用 实例将无法更改 酒吧 (没有使用反射等长度)

    1 回复  |  直到 14 年前
        1
  •  7
  •   Péter Török    14 年前

    最终更新: 所以我的第一个结论碰巧是对的,只是我的推理是错误的:——(我重新编辑了我的答案,使其有点连贯,而不是隐藏我先前的错误的痕迹。

    结论

    正如@wyzard所指出的,即使没有办法改变 bar 施工结束后, Foo 仍然不是线程安全的。问题不在于原子性,而在于可见性。如果线程1正在更改 酒吧 在构造函数中(从其默认值0开始),不保证其他线程何时可以看到新值(或者是否看到它 完全 )

    所以 foo 看起来像一个不可变的对象。引用自 Java Concurrency in Practice ,第3.4节:

    对象是不可变的,如果:

    • 其状态在施工后不能改变;
    • 它的所有领域都是最终的;而且
    • 它是正确构造的(在构造过程中,这个引用不会逃逸)。

    1)和3)看起来不错,但不是2)。这是一个关键点,因为上面的推理。声明变量 final 是确保其在不同线程之间可见性的一种方法。另一种方法是 酒吧 volatile ,或同步其访问方法。但当然,对于不可变对象,这两种方法都没有多大意义。

    最终字段

    那为什么 最终的 字段保证可见性?在实践中从Java并发性中得到答案:

    由于不可变对象非常重要,javamemory模型为 初始化安全 用于共享不可变对象。正如我们所看到的,对象引用对另一个线程可见并不一定意味着该对象的状态对正在使用的线程可见。为了保证对象状态的一致视图,需要同步。

    另一方面,可以安全地访问不可变对象。 即使同步不用于发布对象引用 . 为了保证初始化的安全性,必须满足不变性的所有要求:不可修改状态,所有字段都是最终的,并且构造正确。[…]

    任何线程都可以安全地使用不可变对象,而无需额外的同步,即使不使用同步来发布它们。

    此保证扩展到正确构造的对象的所有最终字段的值;不需要额外的同步就可以安全地访问最终字段。但是,如果final字段引用可变对象,则仍然需要同步才能访问它们引用的对象的状态。

    如果磁场是 决赛?其他线程可能会静默地看到字段的过时值。没有例外或任何警告-这就是为什么这些类型的错误很难跟踪的原因之一。