尽管是可变的,但这在技术上线程安全吗?

发布于 2024-08-31 12:10:24 字数 457 浏览 2 评论 0原文

是的,私有成员变量 bar 应该是 final 对吧?但实际上,在本例中,简单地读取 int 的值是一个原子操作。那么这在技术上是线程安全的吗?

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

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

编辑:

假设这是 Foo 类的所有代码;任何引用 Foo 实例的线程将无法更改 bar 的值(无需使用反射等)

Yes, the private member variable bar should be final right? But actually, in this instance, it is an atomic operation to simply read the value of an int. So is this technically thread safe?

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

// assume infinite number of threads repeatedly calling getBar on the same instance of Foo.

EDIT:

Assume that this is all of the code for the Foo class; any threads with a reference to a Foo instance will not be able to change the value of bar (without going to such lengths as using reflection etc.)

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

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

发布评论

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

评论(1

山有枢 2024-09-07 12:10:25

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

结论

正如 @Wyzard 指出的,即使在构造后无法更改 barFoo 仍然不是线程安全的,问题不在于原子性,而在于线程 1 的可见性。正在更改构造函数中 bar 的值(默认值 0),无法保证其他线程何时会看到新值(或者它们是否根本看到它) )。

所以 foo 看起来像一个不可变的对象,引用自 Java 并发实践。 ,第 3.4 节:

如果满足以下条件,则对象是不可变的:

  • 其状态在构建后无法修改;
  • 其所有字段均为最终字段;和
  • 它的构造正确(this 引用在构造过程中不会转义)。

Foo 在 1) 和 3) 上看起来不错,但在 2) 上则不然。由于上述推理,这是一个关键点。声明变量 final 是确保其在不同线程之间可见性的一种方法。其他方法是声明 bar 易失性,或同步其访问方法。但当然,对于不可变对象,这些都没有多大意义。

Final Fields

那么为什么final字段可以保证可见性呢? Java并发实践中的回答,第3.5.2节:

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

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

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

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

如果该字段不是最终字段,会发生什么情况?其他线程可能会默默地看到该字段的陈旧值。没有例外或任何类型的警告 - 这就是此类错误如此难以追踪的原因之一。

Final update: so my first conclusion happened to be right, just my reasoning was faulty :-( I re-edited my answer to make it somewhat coherent, not to hide the traces of my earlier blunder.

Conclusion

As @Wyzard pointed out, even though there is no way to change bar after construction, Foo is still not thread safe. The problem is not atomicity but visibility. If thread 1 is changing the value of bar in the constructor (from its default value of 0), there is no guarantee when other threads will get to see the new value (or whether they see it at all).

So foo looks like an immutable object. Quoting from Java Concurrency in Practice, section 3.4:

An object is immutable if:

  • Its state cannot be modified after construction;
  • All its fields are final; and
  • It is properly constructed (the this reference does not escape during construction).

Foo looks OK on 1) and 3), but not 2). And that is a crucial point, due to the reasoning above. Declaring a variable final is one way of ensuring its visibility between different threads. The other means are declaring bar volatile, or synchronizing its access method(s). But of course, in case of an immutable object, neither of these would make much sense.

Final Fields

So why do finalfields guarantee visibility? Answer from Java Concurrency in Practice, section 3.5.2:

Because immutable objects are so important, the JavaMemory Model offers a special guarantee of initialization safety for sharing immutable objects. As we've seen, that an object reference becomes visible to another thread does not necessarily mean that the state of that object is visible to the consuming thread. In order to guarantee a consistent view of the object's state, synchronization is needed.

Immutable objects, on the other hand, can be safely accessed even when synchronization is not used to publish the object reference. For this guarantee of initialization safety to hold, all of the requirements for immutability must be met: unmodi-fiable state, all fields are final, and proper construction. [...]

Immutable objects can be used safely by any thread without additional synchronization, even when synchronization is not used to publish them.

This guarantee extends to the values of all final fields of properly constructed objects; final fields can be safely accessed without additional synchronization. However, if final fields refer to mutable objects, synchronization is still required to access the state of the objects they refer to.

And what happens if the field is not final? Other threads may silently see a stale value of the field. There is no exception or any kind of warning - that is one reason why these kinds of bugs are so difficult to trace.

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