在下面的C#代码中,t1总是(我试过的次数)完成。
class MainClass
{
static void DoExperiment ()
{
int value = 0;
Thread t1 = new Thread (() => {
Console.WriteLine ("T one is running now");
while (value == 0) {
//do nothing
}
Console.WriteLine ("T one is done now");
});
Thread t2 = new Thread (() => {
Console.WriteLine ("T two is running now");
Thread.Sleep (1000);
value = 1;
Console.WriteLine ("T two changed value to 1");
Console.WriteLine ("T two is done now");
});
t1.Start ();
t2.Start ();
t1.Join ();
t1.Join ();
}
public static void Main (string[] args)
{
for (int i=0; i<10; i++) {
DoExperiment ();
Console.WriteLine ("------------------------");
}
}
}
但是在非常相似的Java代码中,t1从不(我试过的时候)退出:
public class MainClass {
static class Experiment {
private int value = 0;
public void doExperiment() throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("T one is running now");
while (value == 0) {
//do nothing
}
System.out.println("T one is done now");
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("T two is running now");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
value = 1;
System.out.println("T two changed value to 1");
System.out.println("T two is done now");
}
}
);
t1.start();
t2.start();
t1.join();
t1.join();
}
}
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i++) {
new Experiment().doExperiment();
System.out.println("------------------------");
}
}
}
为什么?
答案 0 :(得分:3)
我不确定它在C#中是如何发生的,但在Java中发生的是JVM
优化。 value
循环中的while
值不会发生变化,而JVM
会识别它,只是跳过测试并将您的咬合代码更改为以下内容:
while (true) {
// do nothing
}
要在java中修复此问题,您需要将value
声明为volatile
:
private volatile int value = 0;
这会使JVM
不优化此while
循环,并在每次迭代开始时检查value
的实际值。< / p>
答案 1 :(得分:2)
这里有几件事。
首先,当你这样做时:
t1.Start ();
t2.Start ();
您要求操作系统安排线程进行运行。 t2
可能会先启动。事实上它甚至可能在t1
计划运行之前完成。
但是,这里存在内存模型问题。您的线程可能在不同的核心上运行。 value
可能位于每个内核的CPU缓存中,或者存储在每个内核的寄存器中,当您读取/写入value
时,您正在写入缓存值。语言运行库不需要将对value
的写入刷新回主存,并且不需要每次都从主存中读取值。
如果要访问共享变量,那么您有责任告诉运行时变量是共享的,并且它必须从主内存读/写和/或刷新CPU缓存。这通常使用C#和Java中的lock
,Interlocked
或synchronized
结构来完成。如果您使用value
(在C#中)或lock
(在Java中)围绕synchronized
的访问权限,那么您应该看到一致的结果。
在没有锁定的情况下表现不同的原因是每种语言都定义了一个内存模型,这些模型是不同的。没有详细说明,x86上的C#写回主内存比Java内存模型更多。这就是为什么你会看到不同的结果。
编辑:有关C#方面的更多信息,请查看Joseph Albahari在C#中的线程Chapter 4。