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

Fin*_*arr 8 java int concurrency

是的,私有成员变量bar应该是final对的吗?但实际上,在这种情况下,简单地读取a的值是一个原子操作int.这在技术上是线程安全吗?

class Foo {
    private int bar;
    public Foo(int bar) {
        this.bar = bar;
    }
    public int getBar() {
        return bar;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

编辑:

假设这是Foo该类的所有代码; 任何引用Foo实例的线程都无法更改bar(不使用反射等长度)

Pét*_*rök 7

最后的更新:所以我的第一个结论恰好是正确的,只是我的推理是错误的:-(我重新编辑了我的答案,使其有些连贯,而不是隐藏我早先的错误的痕迹.

结论

正如@Wyzard指出的那样,即使bar在施工后没有办法改变,Foo仍然不是线程安全的.问题不是原子性,而是可见性.如果线程1正在改变的值bar在构造函数(从0它的默认值),也不能保证当其他线程将能看到新的值(或者他们是否看到它在所有).

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

如果对象是不可变的,则:

  • 施工后其状态不能改变;
  • 它的所有领域都是最终的; 和
  • 它是正确构造的(该参考在施工期间不会逃脱).

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

最后的领域

那么为什么final田地能保证可见度?Java Concurrency in Practice,第3.5.2节的回答:

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

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

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

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

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

  • @Finbarr,这不是在构造之后修改int的问题.您应该阅读[cache coherence](http://en.wikipedia.org/wiki/Cache_coherence).简而言之,在多处理器系统上,当一个处理器写入内存时,除非进行同步,否则其他处理器可能无法立即看到更改.结果是,即使在线程1完成运行构造函数之后,其他线程仍可能在构造函数运行之前看到该内存地址的旧内容一段时间. (2认同)