[英]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.