2

在以下 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("------------------------");
    }
}

}

这是为什么?

4

2 回答 2

3

我不确定它在 C# 中是如何发生的,但在 Java 中发生的是JVM优化。的值在循环value内不会改变并且识别它并且只是跳过测试并将您的咬代码更改为如下所示:whileJVM

while (true) {
    // do nothing
}

为了在java中解决这个问题,你需要声明valuevolatile

private volatile int value = 0;

这将使JVM不优化此循环并在每次迭代开始时while检查 的实际值。value

于 2013-10-31T15:38:00.447 回答
2

这里有几件事。

首先,当你这样做时:

t1.Start ();
t2.Start ();

您要求操作系统安排线程运行。有可能t2会先开始。事实上,它甚至可能在t1计划运行之前完成。

但是,这里存在内存模型问题。您的线程可能在不同的内核上运行。它可能value位于每个内核的 CPU 缓存中,或者存储在每个内核的寄存器中,并且当您读取/写入时,value您正在写入缓存值。语言运行时不需要将写入刷新value到主内存,也不需要每次都从主内存读回值。

如果您想访问共享变量,那么您有责任告诉运行时该变量是共享的,并且它必须从主内存读取/写入和/或刷新 CPU 缓存。这通常使用C# 和 Java 中的 或 结构lockInterlocked完成。如果您使用(在 C# 中)或(在 Java 中)synchronized包围访问,那么您应该会看到一致的结果。valuelocksynchronized

在没有锁定的情况下事情表现不同的原因是每种语言都定义了一个内存模型,而这些模型是不同的。无需详细说明,x86 上的 C# 比 Java 内存模型更多地写回主内存。这就是为什么你会看到不同的结果。

编辑:有关 C# 方面的更多信息,请查看Joseph Albahari 撰写的 C# 中的线程第 4 章。

于 2013-10-31T15:39:38.140 回答