如何测试AtomicBoolean原子性? [英] How to test AtomicBoolean atomicity?

查看:83
本文介绍了如何测试AtomicBoolean原子性?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在编写AtomicInteger和AtomicBoolean的单元测试.它们将用作在Objective-c中测试这些类的仿真的参考测试,以用于翻译的项目中.

I'm writing unit tests for AtomicInteger and AtomicBoolean. They are going to be used as reference tests for testing emulations of these classes in objective-c, for use in translated projects.

我认为AtomicInteger测试的效果很好,基本上是通过在大量for循环中执行可预测数量的递增,递减,加法和减法操作,每个循环都在各自的线程中运行(每种操作类型有多个线程).实际操作同时使用CountDownLatch开始.

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). The actual operations start simultaneously using a 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.

但是如何测试AtomicBoolean?,已经获取并设置了基本操作,因此在多个线程中多次调用并且期望最终结果为true或false似乎没有任何意义.我在想的方向是使用两个应该始终具有相反值的AtomicBoolean.像这样:

But how to test 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. The direction I'm thinking is to use two AtomicBooleans that should always have opposite values. 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();
    }
}

这只能用于一个线程,而不能用于多个线程.我猜这是因为booleanAChanged = booleanA.compareAndSet(!booleanB.get(), booleanB.getAndSet(booleanA.get()));不是一个原子操作.

This works with one thread but fails with multiple. I guess this is because booleanAChanged = booleanA.compareAndSet(!booleanB.get(), booleanB.getAndSet(booleanA.get())); is not one atomic operation.

有什么建议吗?

推荐答案

我将专注于compareAndSet,这是AtomicBoolean与普通boolean之间的真正区别.

I would concentrate on compareAndSet, which is the real difference between an AtomicBoolean and an ordinary boolean.

例如,使用compareAndSet(false, true)控制关键区域.循环执行此操作,直到返回false,然后进入关键区域.在关键区域,如果两个或多个线程同时运行它,则很可能会失败.例如,在读取旧值和写入新值之间的短暂睡眠中增加计数器.在关键区域的末尾,将AtomicBoolean设置为false.

For example, use compareAndSet(false, true) to control a critical region. Loop doing it until it returns false, then enter the critical region. 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,将globalCounter初始化为零.

Initialize the AtomicBoolean to false, and globalCounter to zero before starting the threads.

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是线程数.

At the end, the globalCounter value should be t*iterations where t is the number of threads.

线程数量应与硬件可以同时运行的数量相似-与单处理器相比,在多处理器上发生故障的可能性要大得多.最大的失败风险是在AtomicBoolean变为false之后立即发生.所有可用的处理器都应同时尝试获得对其的独占访问权,将其视为假,然后原子地将其更改为真.

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. All available processors should be simultaneously trying to get exclusive access to it, see it to be false, and atomically change it to true.

这篇关于如何测试AtomicBoolean原子性?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆