AtomicInteger增量不按预期运行

时间:2014-01-25 18:21:30

标签: java multithreading concurrency atomicity atomicinteger

我正在阅读有关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());

}

}

4 个答案:

答案 0 :(得分:3)

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

请尝试使用ConcurrentLinkedQueue

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

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

答案 1 :(得分:1)

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

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

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

答案 2 :(得分:0)

正如在几个答案中所提到的,由于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的最终值,所有其他值都会被覆盖并丢失。

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

答案 3 :(得分:-1)

尝试使用同步集合以这种方式执行此操作。

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