【问题标题】:How Java and C# threads deal with data synchronisation differently?Java 和 C# 线程如何以不同的方式处理数据同步?
【发布时间】:2013-10-31 15:19:01
【问题描述】:

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

}

为什么会这样?

【问题讨论】:

  • 您可能有兴趣阅读 Java 内存模型。
  • 什么是 C# 的编译器设置。你应该试试 x86 Release。

标签: c# java multithreading concurrency synchronization


【解决方案1】:

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

while (true) {
    // do nothing
}

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

private volatile int value = 0;

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

【解决方案2】:

这里有几件事。

首先,当你这样做时:

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

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

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

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

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

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

【讨论】:

  • Without going into the specifics, C# on x86 writes back to main memory more than the Java memory model does - 虽然我对 C# 内存模型没有太多想法,但我严重怀疑它是否需要 T1 来终止。看起来更像是如何优化的实现细节。
猜你喜欢
  • 1970-01-01
  • 2013-04-23
  • 2013-04-07
  • 2023-03-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多