簡體   English   中英

比賽條件:integer 的最小和最大范圍

[英]Race condition: Min and Max range of an integer

我最近在一次采訪中被問到這個問題。

給定以下代碼, static integer 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(如果每次迭代中所有線程之間都存在競爭條件)。
但是面試官說最小值可以 go 甚至低於 5。
這怎么可能?

我聲稱可能的最小值是 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關系,它被稱為包含數據競爭

(線程中的動作之間沒有發生之前的關系)

所以你不能有用地依賴它所做的任何事情。 這只是不正確的代碼。

(此外,我知道這個問題的答案並不是因為一些來之不易的多線程代碼調試或深入的技術閱讀:我知道這一點是因為我以前在其他地方讀過這個答案。這是一個客廳把戲,僅此而已,所以問最小值不是一個很好的面試問題)。

您的線程正在更新一個非易失性變量,這意味着它不能保證每個線程都會看到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刷新到 memory 並且線程 2、3、4、5 決定再次從 memory 讀取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刷新到 memory 之后,Theard 2,3,4.. 將值刷新到 memory 顯示數字的當前值將是3而不是5

在我看來,由於缺乏原子操作,達到 25 是完全不可能的(參見 Java 教程中的 原子訪問)。

所有線程幾乎在同一時間啟動,因此每個線程在第一次迭代中都將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 相差甚遠。

好吧,我的答案是 Max 25,Min 0,因為你所有的操作都是遞增的,並且你將它初始化為 0.. 我認為 static 非易失性 int 被扔在那里讓你 go 進入這些關於種族的想法條件,但是在任何情況下有什么東西會減少這個數字嗎?

編輯:對於它的價值,這將是他們可能希望你能夠在現實世界中克服的典型分心,證明這種“詭計”是正當的,有很多紅鯡魚!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM