在多线程环境中读取的值

时间:2013-02-07 20:19:42

标签: java multithreading

我正在浏览有关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?

由于

4 个答案:

答案 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

  1. T1调用方法writer()f.x已初始化为3,因为它被声明为final,这使得它编译时间常量,因此在类FinalFieldExample的初始化期间将其初始化为给定值,该类在创建类Instance之前发生。由于f未声明为volatile,因此编译器以下列方式重新组合创建对象的步骤序列:(a)f被声明为非null(b){的对象{1}}已创建(b)FinalFieldExample是对该对象的引用。但是在点(a)由T1执行之后,T1被T2抢占
  2. T2调用方法f。它发现reader()不为null,因此它位于if块内。 f已初始化为f.x,因此3已分配给值i。但是3现在已经初始化为默认值f.y,因为在上面给出的步骤1中没有完成对象的构造。因此0被赋值为0。
  3.   

    所以我们看到没有声明变量j是不稳定的   f以这样的方式优化代码的自由   执行对compiler的调用是内联,以及共享   线程constructorf之间共享的变量T1可能会立即更新   一旦已分配存储但在内联构造函数之前   初始化对象。

相关问题