簡體   English   中英

多線程中的 AtomicInteger

[英]AtomicInteger in multithreading

我想找出從 0 到 1000000 的所有質數。為此我寫了這個愚蠢的方法:

public static boolean isPrime(int n) {
    for(int i = 2; i < n; i++) {
        if (n % i == 0)
            return false;
    }
    return true;
}

這對我有好處,不需要任何編輯 比我寫了以下代碼:

private static ExecutorService executor = Executors.newFixedThreadPool(10);
private static AtomicInteger counter = new AtomicInteger(0);
private static AtomicInteger numbers = new AtomicInteger(0);

public static void main(String args[]) {
    long start = System.currentTimeMillis();
    while (numbers.get() < 1000000) {
        final int number = numbers.getAndIncrement();  // (1) - fast
        executor.submit(new Runnable() {
            @Override
            public void run() {
  //               int number = numbers.getAndIncrement(); // (2) - slow
                if (Main.isPrime(number)) {
                    System.out.println("Ts: " + new Date().getTime() + " " + Thread.currentThread() + ": " + number + " is prime!");
                    counter.incrementAndGet();
                }
            }
        });
    }
    executor.shutdown();
    try {
        executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
        System.out.println("Primes: " + counter);
        System.out.println("Delay: " + (System.currentTimeMillis() - start));
    } catch (Exception e) {
        e.printStackTrace();
    }
}

請注意 (1) 和 (2) 標記的行。 當 (1) 啟用時,程序運行得很快,但當 (2) 啟用時,它運行得更慢。

輸出顯示具有大延遲的小部分

Ts: 1480489699692 Thread[pool-1-thread-9,5,main]: 350431 is prime!
Ts: 1480489699692 Thread[pool-1-thread-6,5,main]: 350411 is prime!
Ts: 1480489699692 Thread[pool-1-thread-4,5,main]: 350281 is prime!
Ts: 1480489699692 Thread[pool-1-thread-5,5,main]: 350257 is prime!
Ts: 1480489699693 Thread[pool-1-thread-7,5,main]: 350447 is prime!
Ts: 1480489711996 Thread[pool-1-thread-6,5,main]: 350503 is prime!

和線程得到同等number價值:

 Ts: 1480489771083 Thread[pool-1-thread-8,5,main]: 384733 is prime!
 Ts: 1480489712745 Thread[pool-1-thread-6,5,main]: 384733 is prime!

請解釋為什么選項 (2) 更慢,為什么盡管 AtomicInteger 多線程安全,線程仍然獲得相同的 number 值?

在 (2) 情況下,多達 11 個線程(來自ExecutorService的 10 個線程加上主線程)正在競爭對AtomicInteger訪問,而在情況 (1) 中,只有主線程才能訪問它。 事實上,對於情況 (1),您可以使用int而不是AtomicInteger

AtomicInteger類使用CAS寄存器。 它通過讀取值,執行增量,然后將值與寄存器中的值交換(如果它仍然具有最初讀取的相同值(比較和交換))來完成此操作。 如果另一個線程改變了它重新開始的值:讀取 - 增量 - 比較和交換,直到它成功。

優點是這是無鎖的,因此可能比使用鎖更快。 但它在激烈的競爭下表現不佳。 更多的爭用意味着更多的重試。

編輯

正如@teppic 指出的那樣,另一個問題使案例 (2) 比案例 (1) 慢。 隨着發布的作業中數字的增加,循環條件保持為真的時間比所需的時間長得多。 當 executor 的所有 10 個線程都在忙着確定它們給定的數字是否是素數時,主線程不斷向 executor 發布新作業。 這些新工作在前面的工作完成之前沒有機會增加數字。 因此,當它們在隊列中時, numbers不會增加,並且主線程可以同時完成一個或多個循環,發布新作業。 最終結果是,可以創建和發布的工作崗位比所需的 1000000 個崗位多得多。

你的外循環是: while (numbers.get() < 1000000)

這允許您繼續向主線程中的 ExecutorService 提交比預期更多的 Runnable。

您可以嘗試將循環更改為: for(int i=0; i < 1000000; i++)

(正如其他人提到的,您顯然增加了爭用數量,但我懷疑額外的工作線程是您所看到的減速的更大因素。)

至於你的第二個問題,我很確定兩個子線程看到相同的getAndIncrement值是違反AtomicInteger的約定的。 因此,我在您的代碼示例中沒有看到的其他事情一定會發生。 可能是您看到了程序兩次單獨運行的輸出?

請解釋為什么選項 (2) 更慢?

僅僅是因為您在run() 因此,多個線程將嘗試同時執行此操作,因此會有wait s 和release s。 Bowmore 給出了一個低層次的解釋。

在(1)中它是順序的。 所以不會出現這樣的情況。

盡管 AtomicInteger 多線程安全,為什么線程獲得相同的 number 值?

我不認為有任何可能發生這種情況。 如果有這種情況,它應該從 0 發生。

您在這里錯過了兩個要點:AtomicInteger 的用途以及多線程一般如何工作。
關於為什么選項 2 較慢,@bowmore 已經提供了一個很好的答案。
現在關於打印相同的數字兩次。 AtomicInteger 就像任何其他對象一樣。 您啟動線程,它們會檢查此對象的值。 由於它們與您的主線程競爭,這會增加計數器,因此兩個子線程仍可能看到相同的值。 我會向每個 Runnable 傳遞一個 int 以避免這種情況。

暫無
暫無

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

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