我正在浏览有关Thread和Locks http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5的JLS文档。
class FinalFieldExample {
final int x;
int y;
static FinalFieldExample f;
public FinalFieldExample() {
x = 3;
y = 4;
}
static void writer() {
f = new FinalFieldExample();
}
static void reader() {
if (f != null) {
int i = f.x; // guaranteed to see 3
int j = f.y; // could see 0
}
}
}
我对上面提到的关于f.y如何被视为零的例子(前17.5-1)感到困惑。 Reader Threads将对象f读取为null,在这种情况下它不会执行任何操作,或者它将通过一些引用读取对象f。如果对象f有引用,则构造函数必须已完成其执行,即使多个Writer线程正在运行,以便可以将引用分配给f,如果构造函数已执行,则f.y应该被视为4。
在什么条件下可以f.y = 0?
由于
答案 0 :(得分:5)
在什么条件下可以f.y = 0?
Java内存模型允许JIT编译器重新排序构造函数外非最终字段的初始化。 x
字段是最终字段,因此必须由JVM初始化,但y
不是最终的。因此FinalFieldExample
可能会在static FinalFieldExample f
上分配并设置,但y
字段的初始化尚未完成。
引用17.5-1:
因为writer方法在对象的构造函数完成后写入f,所以读者方法将保证看到f.x的正确初始化值:它将读取值3.但是,f.y不是final;因此,读者方法不能保证看到它的值。
因为f.y
不是最终的,所以无法保证在构造函数完成并分配static f
之前已设置它。因此创建了竞争条件,reader
可能会将y
视为3或0,具体取决于此种族。
答案 1 :(得分:2)
如果一个线程写入一个变量并且另一个线程读取它,那么即使稍后读取发生,第二个线程也可能看不到新值。例如,如果两个线程在不同的处理器上执行,并且写入的值缓存在本地处理器寄存器中,则可能发生这种情况。
Java规范使这种非直观的行为成为可能,以提高性能(如果这是不可能的,那么处理器就无法使用本地内存)
因此,每当您阅读Java内存模型中的“之前发生”关系时,请记住,之前发生的“物理”不一定是在程序逻辑中发生的事情。您需要明确地建立两个线程之间的“before-before”关系,例如通过使用volatile变量进行同步,或者在这种情况下使用final变量。
答案 2 :(得分:1)
不仅是指令重新排序。即使对f.y的写入在f到y之前,也会发生这种情况。对象和类都是人类的烟雾和镜子。在CPU级别,它是所有装载存储器位置和存储器存储器位置。数据最初发送到CPU缓存。让我们假设在这种情况下f转到一个缓存行,f.y转到另一个缓存行。写入程序线程执行其他操作以使第一个高速缓存行(保持f)对其余CPU可见。持有f.y的那个还没有看到(没有任何指示CPU这样做)。内存位置仍为0.当读取器线程在不同的CPU上运行时,它将加载内存位置,因为没有任何信息告诉CPU该位置在另一个CPU缓存中有挂起的更改。这意味着第二个CPU将从内存加载f和f.y. f保存最新值,但最新的f.y仍然在缓存中,因此内存位置保持为0.通过设置volatile,final等,您实际上是在告诉编译器生成代码,告诉CPU发布数据。这只是一个例子,还有更多。
答案 3 :(得分:0)
f.y
可以通过以下方式被视为0
:
假设有两个threads
T1和T2正在运行。 T1正在访问方法writer
,而T2正在加入reader
的同一对象的方法FinalFieldExample
。
writer()
。 f.x
已初始化为3,因为它被声明为final,这使得它编译时间常量,因此在类FinalFieldExample
的初始化期间将其初始化为给定值,该类在创建类Instance之前发生。由于f
未声明为volatile
,因此编译器以下列方式重新组合创建对象的步骤序列:(a)f
被声明为非null(b){的对象{1}}已创建(b)FinalFieldExample
是对该对象的引用。但是在点(a)由T1执行之后,T1被T2抢占f
。它发现reader()
不为null,因此它位于if块内。 f
已初始化为f.x
,因此3
已分配给值i
。但是3
现在已经初始化为默认值f.y
,因为在上面给出的步骤1中没有完成对象的构造。因此0
被赋值为0。所以我们看到没有声明变量
j
是不稳定的f
以这样的方式优化代码的自由 执行对compiler
的调用是内联,以及共享 线程constructor
和f
之间共享的变量T1
可能会立即更新 一旦已分配存储但在内联构造函数之前 初始化对象。