繁体   English   中英

AtomicInteger增量不按预期运行

[英]AtomicInteger incrementation not behaving as expected

我正在阅读有关AtomicInteger及其操作是如何原子的以及这些属性如何使其对多线程有用的内容。

我编写了以下程序来测试相同的程序。

我期望集合的最终大小应该是1000,因为每个线程循环500次并假设每次线程调用getNext()它应该得到一个唯一的数字。

但输出总是小于1000.我在这里缺少什么?

public class Sequencer {

private final AtomicInteger i = new AtomicInteger(0);

public int getNext(){
    return i.incrementAndGet();
}

public static void main(String[] args) {

    final Sequencer seq = new Sequencer();

    final Set<Integer> set = new HashSet<Integer>();

    Thread t1 = new Thread(new Runnable() {
        @Override
        public void run() {
            for (int i=0; i<500; i++)
                set.add(seq.getNext());

        }
    },"T1");
    t1.start();


    Thread t2 = new Thread(new Runnable() {
        @Override
        public void run() {
            for (int i=0; i<500; i++)
                set.add(seq.getNext());

        }
    },"T2");

    t2.start();

    try {
        t1.join();
        t2.join();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    System.out.println(set.size());

}

}

您缺少HashSet不是线程安全的。 此外,集合的属性将擦除所有重复的数字,因此如果AtomicInteger不是线程安全的,则测试将失败。

请尝试使用ConcurrentLinkedQueue

编辑:因为它被问过两次:使用同步集合可以工作,但它破坏了使用像Atomic-classes这样的无锁算法的想法。 如果在上面的代码中用同步集替换了set,​​那么每次调用add线程都必须阻塞。

这将有效地将您的应用程序减少到单线程,因为完成的唯一工作是同步的。 实际上它甚至会比单线程慢,因为synchronized也会造成损失。 因此,如果您想要实际使用线程,请尽量避免synchronized

HashSet不是线程安全的,因此您遇到问题。如果您非常需要使用HashSet,您可以使用Vector或任何线程安全的集合类或顺序运行两个线程。

t1.start();
t1.join();

t2.start();
t2.join();

正如在几个答案中提到的,由于HashSet不是线程安全的,它失败了。

首先,为了您的测试,我们验证AtomicInteger确实是线程安全的,然后继续查看您的测试失败的原因。 稍微修改你的测试。 使用两个哈希集,每个线程一个。 最后,在连接之后,将第二组合并到第一组,通过迭代第二组并将其添加到第一组,这将消除重复(set属性)。 然后对第一组进行计数。 伯爵将是你所期望的。 这证明它是HashSet不是线程安全的而不是AtomicInteger。

那么让我们来看看哪个方面不是线程安全的。 你只做add()s,所以很明显add()是非线程安全的操作导致数字丢失。 让我们看一个示例伪代码非线程安全的HashMap add(),它会丢失数字(这显然不是它实现的方式,只是试图说明它可能是非线程安全的一种方式):

class HashMap {
 int valueToAdd;
 public add(int valueToAdd) {
   this.valueToAdd = valueToAdd;
   addToBackingStore(this.valueToAdd);
 }
}

如果多个线程调用add()并且在更改this.valueToAdd后它们都到达addToBackingStore(),则只添加valueToAdd的最终值,所有其他值都会被覆盖并丢失。

类似的东西可能是你的测试中发生的事情。

尝试使用Collections synchronized以这种方式执行此操作。

public class Sequencer {

    private final AtomicInteger i = new AtomicInteger(0);

    public static void main(String[] args) {

        final Sequencer seq = new Sequencer();

        final Set<Integer> notSafe = new HashSet<Integer>();
        final Set<Integer> set = Collections.synchronizedSet(notSafe);
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 500; i++)
                    set.add(seq.getNext());

            }
        }, "T1");
        t1.start();


        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 500; i++)
                    set.add(seq.getNext());

            }
        }, "T2");

        t2.start();

        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(set.size());

    }

    public int getNext() {
        return i.incrementAndGet();
    }
}

暂无
暂无

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

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