【问题标题】:Race condition: Min and Max range of an integer竞争条件:整数的最小值和最大值范围
【发布时间】:2019-09-29 10:45:32
【问题描述】:

我最近在一次采访中被问到这个问题。

给定以下代码,静态整数num 的最小和最大可能值是多少?

import java.util.ArrayList;
import java.util.List;

public class ThreadTest {
    private static int num = 0;

    public static void foo() {
        for (int i = 0; i < 5; i++) {
            num++;
        }
    }

    public static void main(String[] args) throws Exception{
        List<Thread> threads = new ArrayList<Thread>();
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(new Task());
            threads.add(thread);
            thread.start();
        }
        for (int i = 0; i < 5; i++) {
            threads.get(i).join();
        }
        // What will be the range of num ???
        System.out.println(ThreadTest.num);
    }
}

class Task implements Runnable {
    @Override
    public void run() {
        ThreadTest.foo();
    }

}

我告诉他们最大值是 25(如果没有竞争条件),最小值是 5(如果每次迭代中所有线程之间都存在竞争条件)。
但面试官说最小值甚至可以低于 5。
这怎么可能?

【问题讨论】:

  • 线程 1 在它的第一次迭代 (i == 0) 中读取 num == 0,然后所有其他线程都做他们的事情,除了线程 2,它只进行了前 4 次迭代。然后线程1恢复,将0递增到1并存储它,线程2在最后一次迭代中读取1,线程2在最后一次迭代中读取这个1,线程1完成剩下的工作,线程2递增@ 987654329@ 到 2 并终止。不确定它是否可以低于2
  • 对此的诚实回答是:这并不重要。存在数据竞赛,因此您无法有效地依赖它所做的任何事情。这只是错误的代码。
  • 添加volatile 将使该程序顺序一致,因此从VM 的角度来看没有“竞争”。
  • @JohannesKuhn num++ 不是原子的,所以它甚至不适用于volatile
  • 你有没有问过面试官:这怎么可能?

标签: java multithreading race-condition


【解决方案1】:

我声称可能的最小值是 2。

这其中的关键是num++的非原子性,即它是一个读和一个写,其间可能还有其他操作。

调用线程 T1..T5:

  • T1 读数为 0,T2 读数为 0;
  • T1写1次,然后读写3次。
  • 然后T2写1;
  • 然后 T1 读取 1;
  • 然后 T2-5 完成所有工作
  • 然后,最后,T1 写入 2。

(注意:结果 2 不依赖于线程数或迭代次数,前提是每个线程至少有 2 个。)

但对此的诚实回答是:这真的不重要。 JLS 17.4.5 中定义的数据竞争:

当程序包含两个冲突的访问时(第 17.4.1 节 [“如果至少有一个访问是写入,则对同一变量的两次访问(读取或写入)称为冲突。”])不是按发生前关系排序的,据说它包含数据竞争

(线程中的动作之间没有 happens-before 关系)

所以你不能有效地依赖它所做的任何事情。这只是错误的代码。

(此外,我知道这个问题的答案不是因为一些来之不易的多线程代码调试或深入的技术阅读:我知道这一点是因为我以前在其他地方读过这个答案。这是一个客厅把戏,仅此而已,并且所以问最小值不是一个很好的面试问题)。

【讨论】:

  • 我认为“happens before relation”是单线程动作。例如。如果在 main 方法中,int a = num; int b = num; 在代码中它看起来像 aa=num 发生在b=num 之前?与此示例相反,此示例更多的是一般竞争条件,您不知道每个线程访问变量的顺序。
  • @matt program order 隐含了一种happens-before 关系,也就是说,在单个线程中,事情似乎按照它们编写的顺序发生。这只是没有告诉您有关线程之间看到的值的任何有用信息。
  • 这就像问一个结构工程师“这是一座用纸牌建造的塔。请描述一下,一阵风吹来并倒塌后,会留下多少纸牌。”
  • @DanChase 我不关注。所有线程都递增,您等待所有线程完成,因此它不能保持为零。
  • @Cristik T1 被抢占然后恢复的机制是什么,它允许它不循环 5 次?
【解决方案2】:

您的线程正在更新一个非易失性变量,这意味着它不能保证每个线程都会看到num 的更新值。让我们考虑以下线程的执行流程:

Thread 1: 0->1->2 (2 iteration left)
Thread 2: 0->1->2->3 (1 iteration left)
Thread 3: 0->1->2->3 (1 iteration left)
Thread 4: 0->1->2->3 (1 iteration left)
Thread 5: 0->1->2->3 (1 iteration left)

此时,线程 1 将 num 的值 2 刷新到内存中,线程 2,3,4,5 决定再次从内存中读取 num(出于任何原因)。现在:

Thread 1: 2->3->4 (completed 2 iteration)
Thread 2: 2->3 (completed 1 iteration)
Thread 3: 2->3 (completed 1 iteration)
Thread 4: 2->3 (completed 1 iteration)
Thread 5: 2->3 (completed 1 iteration)

线程 1 将值 4 刷新到内存,然后 Theard 2,3,4.. 将值刷新到内存显示当前数字值将是 3 而不是 5

【讨论】:

    【解决方案3】:

    在我看来,由于缺乏原子操作(参见 Java 教程中的Atomic Access),达到 25 是完全不可能的。

    所有线程几乎同时启动,因此每个线程在第一次迭代中将ThreadTest.num 值视为0。由于有 5 个线程并行访问同一个变量,因此在第三次迭代中,线程可能会看到ThreadTest.num 的值仍为12,并且会错误地递增到23

    根据硬件,最终值会更低或更高,最快的可能有最低的值,最慢的可能有更高的值。但是,我的主张是最大值不能达到 25。

    编辑 (2019-10-07)

    我在我的机器(Core i5 HQ)上测试了自己,确实最终结果几乎每次都达到25。为了更好地理解,我在for 循环中测试了一个更大的数字:

    for (int i = 0; i < 10000; i++) {
        num++;
    }
    

    现在,大多数情况下,最终结果在 20000 到 30000 之间,与 50000 相差甚远。

    【讨论】:

    • 好吧,我实际上多次运行了该程序,并且大部分时间都打印了 25 次。
    • 嗯,我没有测试就回答了。现在我评估了自己,我认为你是对的,这对我来说没有意义。也许那是因为循环太小了,代码每次都在新的 JVM 中运行。
    • 我添加了一条关于我的错误的注释和一个更大数字的测试。
    【解决方案4】:

    好吧,我的答案是 Max 25,Min 0,因为你所有的操作都是递增的,你把它初始化为 0.. 我认为静态非易失 int 被扔在那里让你进入这些关于比赛条件的想法,但有什么东西会在任何情况下减少这个数字吗?

    编辑:不管怎样,这将是一种典型的干扰,他们可能希望你能够在现实世界中克服,证明这种“诡计”是正当的,有很多红鲱鱼!

    【讨论】:

    • 我相信提问者/面试官想知道最后的最小值和最大值是多少(即使问题没有明确说明)。否则这个问题将是微不足道的,可能不会在面试中被问到。
    • @Dukeling 我同意如果它没有初始化为 0 .. 以下 [0,1,2,3] 的最小集是 0,不是吗?可能是错的,谁真的知道他们想要什么 :) 你可能是对的。编辑:对于它的价值,我会专门问这个问题,看看这个人是否生活在“杂草丛中”。这个问题的另一个用途(在我看来更合理)是让他们展示他们如何分析它,而不是一个特定的答案,因为它似乎可以是 0 到 25 之间的任何值,具体取决于因素/时间/等。
    猜你喜欢
    • 2014-03-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-27
    • 2014-11-14
    • 2016-08-31
    • 1970-01-01
    相关资源
    最近更新 更多