【问题标题】:Why is there no race condition in this java code?为什么这个 java 代码中没有竞争条件?
【发布时间】:2014-09-01 00:17:03
【问题描述】:

这是我的主题:

public class MyRunnable implements Runnable
{
  public static int num = 0;

  private void add()
  {
    num = num + 1;
  }

  @Override
  public void run()
  {
    for (int i=0;i<10000;i++)
    {
      add();
      System.out.println(num);
    }
  }
}

这是我的主要内容:

public class MultiThread
{
  public static void main(String[] argv)
  {
    Thread mt1 = new Thread(new MyRunnable(), "A");
    Thread mt2 = new Thread(new MyRunnable(), "B");

    mt1.start();
    mt2.start();
  }

}

我希望在那里看到竞态条件,因此输出应该小于 20000。但是,我得到的实际输出是:

19975
19976
19977
19978
19979
19980
19981
19982
19983
19984
19985
19986
19987
19988
19989
19990
19991
19992
19993
19994
19995
19996
19997
19998
19999
20000

Process finished with exit code 0

谁能向我解释为什么在这个 java 程序中添加操作似乎是原子的,即使我没有进行任何锁定或同步?

【问题讨论】:

  • 结果中没有错误并不意味着没有(可能的)竞争条件。
  • 我多次运行这段代码,从未遇到过竞态条件,但我认为在超过数十万次的迭代中没有获得竞态条件,我认为我没有那么幸运。跨度>
  • 尝试增加迭代次数(让我们说1000000)并将打印语句移到循环之外(将它放在它后面以增加竞争的机会)。
  • AtomicInt存在是有原因的!

标签: java multithreading parallel-processing


【解决方案1】:

您只是尝试的次数不够多,或者您的结果查看的不够仔细。这段代码

private void add()
{
    num = num + 1;
}

绝对不安全。您正在为丢失的更新做好准备。两个线程将读取相同的 num 值,并且每个线程都会更新它,因此会丢失一个增量。

【讨论】:

  • 有可能每个线程在 I/O 上花费了很多 更多 时间,以至于它们在相对微不足道的时间内不会碰巧相互踩踏做一个单一的加载修改存储。
  • @GregHewgill 我明白了。是的,在我删除打印语句后出现竞争条件。
  • @OneZero 看看this 以及System.out.println() 的效果。
【解决方案2】:

第一个线程很可能在第二个线程开始自己的循环之前完成其(相当短的)循环,因此它们似乎不会相互干扰。

如果您尝试更长的循环或在循环中添加延迟,您会看到预期的行为。

【讨论】:

    【解决方案3】:

    在您提出的案例中,为了更明确地说明竞争条件的概念;问题当然是面积

    数 = 数 + 1;

    要澄清为什么这是非常不安全的并且会导致竞争条件,您需要查看定义它的assembly code。现在,在 Java 中,您可能认为这只是行正在执行的代码,但事实并非如此。让我解释一下……

    考虑以下装配线

    LOAD    @i, r0    ;load the value of 'i' into a register from memory
    ADD     r0, 1     ;increment the value in the register
    STORE   r0, @i    ;write the updated value back to memory
    

    或者简单来说:

    Fetch i into a register
    Increment the register
    Write it back to i
    

    当线程 A 将 i 提取到寄存器中时,会发生争用情况,将其递增,然后在回写之前 - 线程 B 进来并执行相同的操作 - 提取和递增(哦,不)。可以在3行代码中的任何地方

    所以这段代码被认为是不安全的并且可能导致竞争条件。它不会在第 1000 次发生的事实并不意味着它不会发生。

    【讨论】: