争用条件:整数的最小值和最大值范围

2022-09-01 10:56:46

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

给定以下代码,静态整数的最小和最大可能值是多少?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

我声称可能的最小值为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 [“如果至少有一个访问是写入的,则对同一变量的两个访问(读取或写入)被认为是冲突的。”]),这两个访问不是按发生之前的关系排序的,因此它被称为包含数据竞跑

(线程中的操作之间不存在“发生之前”关系)

因此,您不能有效地依赖它所做的任何事情。它只是不正确的代码。

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


答案 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,3,4,5 决定再次从内存中读取 (出于任何原因)。现在:2num

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 将值刷新到内存,之后将值刷新到内存中,之后为 Theard 2,3,4。将值刷新到内存中,显示当前值的数字将代替435