简体   繁体   English

如何测试AtomicBoolean原子性?

[英]How to test AtomicBoolean atomicity?

I'm writing unit tests for AtomicInteger and AtomicBoolean. 我正在为AtomicInteger和AtomicBoolean编写单元测试。 They are going to be used as reference tests for testing emulations of these classes in objective-c, for use in translated projects. 它们将用作参考测试,用于在objective-c中测试这些类的仿真,以用于翻译项目。

The AtomicInteger test worked out well I think, basically by performing a predictable number of increment, decrement, add and subtract operations in a large number of for loops, each running in their own thread (and many threads per operation type). 我认为AtomicInteger测试很好,主要是通过在大量for循环中执行可预测数量的递增,递减,加法和减法操作,每个循环都在自己的线程中运行(每个操作类型有很多线程)。 The actual operations start simultaneously using a CountDownLatch. 实际操作使用CountDownLatch同时启动。

When all threads are done I assert by comparing the atomic integer with the expected integer value based on the number of threads, iterations per thread and the expected increase/decrease per iteration. 当所有线程完成后,我通过比较原子整数和预期的整数值来断言,基于线程数,每个线程的迭代次数和每次迭代的预期增加/减少。 This test passes. 这个测试通过。

But how to test AtomicBoolean? 但是如何测试AtomicBoolean? The basic operations are get and set so calling that many times in many threads and expecting the final result to be either true or false doesn't seem to make sense. 基本操作是get和set,因此在许多线程中多次调用并期望最终结果为true或false似乎没有意义。 The direction I'm thinking is to use two AtomicBooleans that should always have opposite values. 我正在考虑的方向是使用两个应始终具有相反值的AtomicBooleans。 Like this: 像这样:

@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();
    }
}

This works with one thread but fails with multiple. 这适用于一个线程,但失败了多个。 I guess this is because booleanAChanged = booleanA.compareAndSet(!booleanB.get(), booleanB.getAndSet(booleanA.get())); 我想这是因为booleanAChanged = booleanA.compareAndSet(!booleanB.get(), booleanB.getAndSet(booleanA.get())); is not one atomic operation. 不是一个原子操作。

Any suggestions? 有什么建议么?

I would concentrate on compareAndSet , which is the real difference between an AtomicBoolean and an ordinary boolean . 我将专注于compareAndSet ,这是AtomicBoolean和普通boolean之间的真正区别。

For example, use compareAndSet(false, true) to control a critical region. 例如,使用compareAndSet(false, true)来控制关键区域。 Loop doing it until it returns false, then enter the critical region. 循环执行直到它返回false,然后进入临界区域。 In the critical region, do something that is very likely to fail if two or more threads run it at the same time. 在关键区域,如果两个或多个线程同时运行,则执行极有可能失败的操作。 For example, increment a counter with a short sleep between reading the old value and writing the new value. 例如,在读取旧值和写入新值之间增加一个具有短睡眠的计数器。 At the end of the critical region, set the AtomicBoolean to false. 在关键区域的末尾,将AtomicBoolean设置为false。

Initialize the AtomicBoolean to false, and globalCounter to zero before starting the threads. 初始化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);
}

At the end, the globalCounter value should be t*iterations where t is the number of threads. 最后, globalCounter值应为t*iterations ,其中t是线程数。

The number of threads should be similar to the number the hardware can run simultaneously - and this is far more likely to fail on a multiprocessor than on a single processor. 线程数应该与硬件可以同时运行的数量相似 - 这在多处理器上比在单个处理器上失败的可能性要大得多。 The highest risk of failure is immediately after the AtomicBoolean becomes false. 失败的最高风险是在AtomicBoolean变为false之后。 All available processors should be simultaneously trying to get exclusive access to it, see it to be false, and atomically change it to true. 所有可用的处理器应该同时尝试获取它的独占访问权限,看它是错误的,并自动将其更改为true。

I think it's going to be harder to test this than an AtomicInteger , as you point out. 我认为,正如你指出的那样,测试这个比AtomicInteger更难。 The space of possible values is much smaller, and thus the space of possible things-that-can-go-wrong is much smaller. 可能值的空间要小得多,因此可能出错的可能事物的空间要小得多。 Since tests like this basically come down to luck (with lots of looping to increase your chances), it's going to be harder to hit that smaller target. 因为这样的测试基本上归结为运气(通过大量循环来增加你的机会),所以更难以击中那个更小的目标。

My recommendation would be to launch a lot of threads that have access to a single AtomicBoolean . 我的建议是启动许多可以访问单个AtomicBoolean的线程。 Have each one of them do a CAS, and only if they succeed, atomically increment an AtomicInteger . 让他们每个人做CAS,并且只有在他们成功的情况下,以原子方式递增AtomicInteger When all the threads have finished, you should see just a single increment to that AtomicInteger . 当所有线程都完成后,您应该只看到AtomicInteger的一个增量。 Then just rinse, lather, repeat. 然后冲洗,起泡,重复。

It is four atomic operations. 这是四个原子操作。 Given you just want one boolean to be the inverse of the other, just have one boolean and keep toggling that. 鉴于你只想让一个布尔值与另一个布尔值相反,只需要一个布尔值并继续切换它。 You can calculate the other from this value. 您可以从此值计算另一个。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM