我曾经认为,在两个线程之间共享的任何变量都可以在线程本地缓存,并且应该声明为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对象的变量“value”,并在将值设置为1时更新它的副本。在这种情况下,主线程可能看不到更新的值。
但是我的队友认为,由于对“值”的访问是通过对象(worker)进行的,因此两个线程都可以看到不同的值,只有两个线程都维护“worker”的单独副本才有可能“对象本身(这将进一步意味着创建一个线程涉及创建所有共享对象的深层副本)。
现在我知道这不可能是真的,因为每个线程维护所有共享对象的完全不同的副本是非常低效的。因此,我非常怀疑。在主线程中执行“worker.value”是否引用了与在子线程中执行“this.value”不同的内存位置?子(Worker)线程会缓存“值”吗?
问候。
答案 0 :(得分:7)
现在我的论点是,Worker(子)线程可能已经缓存了变量" value"工作对象线程本地并在将值设置为1时更新它的副本。在这种情况下,主线程可能看不到更新的值。
你是对的。即使您正在处理相同的Worker
实例,也无法保证Worker
字段的缓存内存版本已在各种不同的线程内存缓存之间同步。 / p>
必须将value
字段标记为volatile
,以确保其他线程会看到value = 1;
字段的value
更新。
但我的队友认为,自从获得"价值"正在通过一个对象(worker)发生,因此两个线程都可以看到不同的值,只有两个线程都维护" worker"对象本身......
不,这不正确。线程内存的棘手部分围绕处理器内存缓存。如果没有volatile
强加的内存屏障,进程可以完全自由地缓存内存。因此,即使两个线程都使用Worker
的相同实例,它们也可能具有与Worker
关联的内存的本地缓存副本。
线程架构获得了很大的速度,因为它们使用独立的高速处理器本地内存,而不是始终引用中央存储。
答案 1 :(得分:2)
但是我的队友认为,由于对“值”的访问是通过对象(worker)进行的,因此两个线程都可以看到不同的值,只有两个线程都维护“worker”的单独副本才有可能“对象本身(这将进一步意味着创建一个线程涉及创建所有共享对象的深层副本)。
你的同事没有意识到的是实例变量的值(这个问题的任何变量)可以暂时缓存在机器寄存器中,或缓存在处理器的第一级或第二级内存缓存中。 Java语言规范明确指出,除非已采取适当的步骤,否则两个线程不一定会看到相同变量的相同值。
JLS的一部分内容涉及此问题:JLS 17.4。我建议您和您的同事阅读本文以及17.5和17.6,如果您要讨论Java在此领域的行为方式。或者你可以阅读Brian Goetz等人的“Java Concurrency in Practice”的最后一章,它比JLS更容易阅读。
我建议你和你的同事不要依赖你对线程应该工作的直觉。阅读规格。线程行为的某些方面不直观 ......尽管它们的方式有很好的理由,