最终更新:
所以我的第一个结论碰巧是对的,只是我的推理是错误的:——(我重新编辑了我的答案,使其有点连贯,而不是隐藏我先前的错误的痕迹。
结论
正如@wyzard所指出的,即使没有办法改变
bar
施工结束后,
Foo
仍然不是线程安全的。问题不在于原子性,而在于可见性。如果线程1正在更改
酒吧
在构造函数中(从其默认值0开始),不保证其他线程何时可以看到新值(或者是否看到它
完全
)
所以
foo
看起来像一个不可变的对象。引用自
Java Concurrency in Practice
,第3.4节:
对象是不可变的,如果:
-
其状态在施工后不能改变;
-
它的所有领域都是最终的;而且
-
它是正确构造的(在构造过程中,这个引用不会逃逸)。
富
1)和3)看起来不错,但不是2)。这是一个关键点,因为上面的推理。声明变量
final
是确保其在不同线程之间可见性的一种方法。另一种方法是
酒吧
volatile
,或同步其访问方法。但当然,对于不可变对象,这两种方法都没有多大意义。
最终字段
那为什么
最终的
字段保证可见性?在实践中从Java并发性中得到答案:
由于不可变对象非常重要,javamemory模型为
初始化安全
用于共享不可变对象。正如我们所看到的,对象引用对另一个线程可见并不一定意味着该对象的状态对正在使用的线程可见。为了保证对象状态的一致视图,需要同步。
另一方面,可以安全地访问不可变对象。
即使同步不用于发布对象引用
. 为了保证初始化的安全性,必须满足不变性的所有要求:不可修改状态,所有字段都是最终的,并且构造正确。[…]
任何线程都可以安全地使用不可变对象,而无需额外的同步,即使不使用同步来发布它们。
此保证扩展到正确构造的对象的所有最终字段的值;不需要额外的同步就可以安全地访问最终字段。但是,如果final字段引用可变对象,则仍然需要同步才能访问它们引用的对象的状态。
如果磁场是
不
决赛?其他线程可能会静默地看到字段的过时值。没有例外或任何警告-这就是为什么这些类型的错误很难跟踪的原因之一。