发布时,没有状态的对象是否始终可见?

时间:2018-01-02 04:15:04

标签: java multithreading concurrency visibility safe-publication

说我有一个班级

public class Foo {
    public void printHi() {
       System.out.print("Hi");
    }
}

在某些客户端代码中我执行类似

的操作
public static void main() {
     Foo foo = new Foo();
     (new Thread(() -> {foo.printHi();})).start();
}

并取消调用Thread Start的事先保证。 那么Foo引用可能对于使用它的那个线程是不可见的,或者更糟糕的是,属于该类的方法是不可见的,但是Foo引用是可见的。我不确定方法是如何存储在像字段这样的对象中,但是这假设它只是属于该对象的内存中的某些内容,因此可能存在可见性问题,但我不确定。有人也可以向我解释那部分吗?

我问这个因为Foo是不可变的,而且在JCIP的书中,Goetz说

另一方面,即使不使用同步来发布对象引用,也可以安全地访问不可变对象。为了保证初始化安全性的保持,必须满足所有不可变性的要求:无法改变的状态,所有领域都是最终的,正确的建设“(Goetz,3.5.2)

但是,它没有任何最终字段,所以它是否算作所有字段都是最终字段?既然没有字段=所有字段?

2 个答案:

答案 0 :(得分:2)

有不同的方法可以得到相同的答案。

  1. 您的对象是不可变的,因为它没有可以修改的状态。
  2. 其所有字段均为final,因为没有字段final
  3. 没有可能的竞争条件,因为在访问时没有可以修改的数据。
  4. 即使在final中声明了一些非Foo字段,但printHi()的调用(不读取对象的状态)也没有潜在的数据竞争。请注意,这仅适用于由Foo表达式生成的new Foo(…)的精确实例,因为子类可以覆盖printHi()以访问共享的可变状态。

    重要的是要强调竞争条件是关于共享的可变数据,而不一定是对象。因此,如果printHi()访问不同类的共享static变量,即使Foo实例不可变和/或正确发布,它也可能产生数据争用。如果foo.printHi()不访问共享可变状态(或仅使用适当的保护),则在另一个线程中调用printHi()的代码是安全的。

  5. 作为Elliott Frisch mentioned,lambda表达式的行为就像一个不可变对象,所以即使没有{em>发生在之前的关系或者不可变性,代码也是安全的。 Thread.start()(假设之后未修改Foo实例)。

答案 1 :(得分:1)

foo 必须(有效)final才能在此处使用。

Foo foo = null; // <-- for example,
foo = new Foo();
(new Thread(() -> {
    foo.printHi(); // <-- compiler error
})).start();