给定一个带有变量x的A类。变量x在类构造函数中设置:
A() {
x = 77;
}
我们想要将x发布到其他线程。考虑以下3个变量x线程安全(?)发布的案例:
1)x 最终
2)x 易失性
3)x在同步块
中设置synchronized(someLock) {
A a = new A();
a.x = 77;
}
Thread2只打印x:
System.out.println(a.x);
问题是:是否可以观察到Thread2打印的'0'?或者JMM保证在所有3种情况下都会打印'77'或者会抛出NPE?
答案 0 :(得分:3)
我的回答来自best page on the internet,特别是第17章涉及内存可见性和并发性。
我也假设您没有引用泄漏(即,在对象构造函数完成之前,您没有对该对象的引用)。
最终字段。我将在上面引用上面的章节,第17.5章:
当构造函数完成时,对象被认为是完全初始化的。在该对象完全初始化之后只能看到对象引用的线程可以保证看到该对象的最终字段的正确初始化值。
挥发性。我再次引用JLS:
对易失性变量v(第8.3.1.4节)的写入与任何线程对v的所有后续读取同步(其中“后续”根据同步顺序定义)。
因此,假设您的Thread
在完成构造函数后可以访问该对象,它将看到正确的值。注意,这意味着你可能也需要制作一个易变的。
根据定义,如果读取和写入之间存在Happens-before
关系,则保证可见。否则,可能发生您看到未初始化的值。什么构成Happens-before
关系?同样,JLS第17章指定了这一点,特别是:
因此可能有两种情况:
A a = new A();
Thread t = new MyThread(a);
t.start();
MyThread
保存A的实例并使用它。在这种情况下,线程是在a
之后创建的,并且在创建后调用start()
。因此,即使x
是非易失性的,也可以保证可见性。但是,无法保证x
进一步更改的可见性。
情况2:
这有点难以编码,但是:
Main创建两个Threads并立即启动它们,并且有一个类型为A的非易失性字段
ThreadA创建A并将其写入共享字段
ThreadB循环一段时间,直到填充字段然后打印出x。
在这种情况下,即使写入x和写入共享字段之间存在HB,共享字段的读取和写入之间也不存在HB。因此,无法保证写入x的可见性。
正如所承诺的那样 - 如果在这两种情况中的任何一种情况下在x的写入周围放置一个同步块,它将不会影响结果,因为监视器上没有其他任何锁定。锁定和解锁同一监视器会创建同步操作,从而创建HB关系。