簡體   English   中英

如何測試AtomicBoolean原子性?

[英]How to test AtomicBoolean atomicity?

我正在為AtomicInteger和AtomicBoolean編寫單元測試。 它們將用作參考測試,用於在objective-c中測試這些類的仿真,以用於翻譯項目。

我認為AtomicInteger測試很好,主要是通過在大量for循環中執行可預測數量的遞增,遞減,加法和減法操作,每個循環都在自己的線程中運行(每個操作類型有很多線程)。 實際操作使用CountDownLatch同時啟動。

當所有線程完成后,我通過比較原子整數和預期的整數值來斷言,基於線程數,每個線程的迭代次數和每次迭代的預期增加/減少。 這個測試通過。

但是如何測試AtomicBoolean? 基本操作是get和set,因此在許多線程中多次調用並期望最終結果為true或false似乎沒有意義。 我正在考慮的方向是使用兩個應始終具有相反值的AtomicBooleans。 像這樣:

@Test
public void testAtomicity() throws Exception {

    // ====  SETUP  ====
    final AtomicBoolean booleanA = new AtomicBoolean(true);
    final AtomicBoolean booleanB = new AtomicBoolean(false);

    final int threadCount = 50;

    final int iterationsPerThread = 5000;

    final CountDownLatch startSignalLatch = new CountDownLatch(1);
    final CountDownLatch threadsFinishedLatch = new CountDownLatch(threadCount);

    final AtomicBoolean assertFailed = new AtomicBoolean(false);

    // ====  EXECUTE: start all threads ====
    for (int i = 0; i < threadCount; i++) {

        // ====  Create the thread  =====
        AtomicOperationsThread thread;
        thread = new AtomicOperationsThread("Thread #" + i, booleanA, booleanB, startSignalLatch, threadsFinishedLatch, iterationsPerThread, assertFailed);
        System.out.println("Creating Thread #" + i);

        // ====  Start the thread (each thread will wait until the startSignalLatch is triggered)  =====
        thread.start();
    }

    startSignalLatch.countDown();

    // ====  VERIFY: that the AtomicInteger has the expected value after all threads have finished  ====
    final boolean allThreadsFinished;
    allThreadsFinished = threadsFinishedLatch.await(60, TimeUnit.SECONDS);

    assertTrue("Not all threads have finished before reaching the timeout", allThreadsFinished);
    assertFalse(assertFailed.get());

}

private static class AtomicOperationsThread extends Thread {

    // #####  Instance variables  #####

    private final CountDownLatch startSignalLatch;
    private final CountDownLatch threadsFinishedLatch;

    private final int iterations;

    private final AtomicBoolean booleanA, booleanB;

    private final AtomicBoolean assertFailed;

    // #####  Constructor  #####

    private AtomicOperationsThread(final String name, final AtomicBoolean booleanA, final AtomicBoolean booleanB, final CountDownLatch startSignalLatch, final CountDownLatch threadsFinishedLatch, final int iterations, final AtomicBoolean assertFailed) {

        super(name);
        this.booleanA = booleanA;
        this.booleanB = booleanB;
        this.startSignalLatch = startSignalLatch;
        this.threadsFinishedLatch = threadsFinishedLatch;
        this.iterations = iterations;
        this.assertFailed = assertFailed;
    }

    // #####  Thread implementation  #####

    @Override
    public void run() {

        super.run();

        // ====  Wait for the signal to start (so all threads are executed simultaneously)  =====
        try {
            System.out.println(this.getName() + " has started. Awaiting startSignal.");
            startSignalLatch.await();  /* Awaiting start signal */
        } catch (InterruptedException e) {
            throw new RuntimeException("The startSignalLatch got interrupted.", e);
        }

        // ====  Perform the atomic operations  =====
        for (int i = 0; i < iterations; i++) {

            final boolean booleanAChanged;
            booleanAChanged = booleanA.compareAndSet(!booleanB.get(), booleanB.getAndSet(booleanA.get()));  /* Set A to the current value of B if A is currently the opposite of B, then set B to the current value of A */

            if (!booleanAChanged){
                assertFailed.set(true);
                System.out.println("Assert failed in thread: " + this.getName());
            }
        }

        // ====  Mark this thread as finished  =====
        threadsFinishedLatch.countDown();
    }
}

這適用於一個線程,但失敗了多個。 我想這是因為booleanAChanged = booleanA.compareAndSet(!booleanB.get(), booleanB.getAndSet(booleanA.get())); 不是一個原子操作。

有什么建議么?

我將專注於compareAndSet ,這是AtomicBoolean和普通boolean之間的真正區別。

例如,使用compareAndSet(false, true)來控制關鍵區域。 循環執行直到它返回false,然后進入臨界區域。 在關鍵區域,如果兩個或多個線程同時運行,則執行極有可能失敗的操作。 例如,在讀取舊值和寫入新值之間增加一個具有短睡眠的計數器。 在關鍵區域的末尾,將AtomicBoolean設置為false。

初始化AtomicBoolean為false,並且globalCounter啟動線程之前為零。

for(int i=0; i<iterations; i++) {
  while (!AtomicBooleanTest.atomic.compareAndSet(false, true));
  int oldValue = AtomicBooleanTest.globalCounter;
  Thread.sleep(1);
  AtomicBooleanTest.globalCounter = oldValue + 1;
  AtomicBooleanTest.atomic.set(false);
}

最后, globalCounter值應為t*iterations ,其中t是線程數。

線程數應該與硬件可以同時運行的數量相似 - 這在多處理器上比在單個處理器上失敗的可能性要大得多。 失敗的最高風險是在AtomicBoolean變為false之后。 所有可用的處理器應該同時嘗試獲取它的獨占訪問權限,看它是錯誤的,並自動將其更改為true。

我認為,正如你指出的那樣,測試這個比AtomicInteger更難。 可能值的空間要小得多,因此可能出錯的可能事物的空間要小得多。 因為這樣的測試基本上歸結為運氣(通過大量循環來增加你的機會),所以更難以擊中那個更小的目標。

我的建議是啟動許多可以訪問單個AtomicBoolean的線程。 讓他們每個人做CAS,並且只有在他們成功的情況下,以原子方式遞增AtomicInteger 當所有線程都完成后,您應該只看到AtomicInteger的一個增量。 然后沖洗,起泡,重復。

這是四個原子操作。 鑒於你只想讓一個布爾值與另一個布爾值相反,只需要一個布爾值並繼續切換它。 您可以從此值計算另一個。

暫無
暫無

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

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