6

我曾经相信两个线程之间共享的任何变量都可以在线程本地缓存,并且应该声明为 volatile。但这种信念最近受到队友的挑战。我们试图弄清楚在以下情况下是否需要 volatile。

class Class1
{
   void Method1()
   {
      Worker worker = new Worker();
      worker.start();
      ...
      System.out.println(worker.value); // want to poll value at this instant
      ...
   }

   class Worker extends Thread
   {
      int value = 0; // Should this be declared as a volatile?
      public void run()
      {
         ...
         value = 1; // this is the only piece of code that updates value
         ...
       }
   }
}

现在我的论点是,Worker(子)线程可能已经在线程中缓存了 Worker 对象的变量“值”,并在将值设置为 1 时仅更新了它的副本。在这种情况下,主线程可能看不到更新的值。

但是我的队友认为,由于对“值”的访问是通过对象(工作者)发生的,因此对于两个线程来说,看到不同的值,只有当两个线程都维护“工作者”对象本身的单独副本时才有可能(这进一步意味着创建线程涉及创建所有共享对象的深层副本)。

现在我知道这不可能是真的,因为每个线程维护所有共享对象的完全不同的副本是非常低效的。因此,我对此表示严重怀疑。在主线程中执行“worker.value”是否引用了与在子线程中执行“this.value”不同的内存位置?子(Worker)线程会缓存“值”吗?

问候。

4

2 回答 2

7

现在我的论点是,Worker(子)线程可能已经在本地缓存了 Worker 对象线程的变量“值”,并在将值设置为 1 时仅更新它的副本。在这种情况下,主线程可能看不到更新的值。

你是对的。即使你们都在处理同一个Worker实例,也不能保证Worker's 字段的缓存内存版本已在各种不同的线程内存缓存之间同步。

value字段必须标记为volatile保证其他线程将看到value = 1;该字段的更新value

但是我的队友认为,由于对“值”的访问是通过对象(工作者)发生的,因此对于两个线程来说,看到不同的值,只有当两个线程都维护“工作者”对象本身的单独副本时才有可能...

不,这是不正确的。关于线程内存的棘手部分围绕处理器内存缓存展开。如果没有由 强加的内存屏障,volatile进程可以完全自由地缓存内存。因此,即使两个线程都在使用 的同一个实例Worker,它们也可能拥有Worker.

线程架构获得了很大的速度,因为它们使用单​​独的高速处理器本地内存,而不是始终引用中央存储。

于 2012-06-15T16:01:17.160 回答
2

但是我的队友认为,由于对“值”的访问是通过对象(工作者)发生的,因此对于两个线程来说,看到不同的值,只有当两个线程都维护“工作者”对象本身的单独副本时才有可能(这进一步意味着创建线程涉及创建所有共享对象的深层副本)。

您的同事没有意识到实例变量(与此相关的任何变量)的值可以临时缓存在机器寄存器或处理器的第一级或第二级内存缓存中。Java 语言规范明确指出,两个线程不一定会看到相同变量的相同值,除非它们采取了适当的步骤。

JLS 有一个完整的部分处理这个问题:JLS 17.4。如果您要讨论 Java 在这方面的表现,我建议您和您的同事都阅读本文以及 17.5 和 17.6。或者您可以阅读 Brian Goetz 等人撰写的“Java Concurrency in Practice”的最后一章,它比 JLS 更容易阅读。

我建议您和您的同事不要依赖您对线程应该起作用的直觉。阅读规格。线程行为的某些方面并不直观......尽管有充分的理由说明它们是这样的,

于 2012-06-15T16:21:23.873 回答