3

我正在阅读 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

4 回答 4

3

您错过了 HashSet 不是线程安全的。此外,集合的属性会删除所有重复的数字,因此如果 AtomicInteger 不是线程安全的,您的测试将失败。

尝试改用ConcurrentLinkedQueue

编辑:因为它已被问过两次:使用同步集有效,但它破坏了使用像原子类这样的无锁算法背后的想法。如果在上面的代码中将集合替换为同步集合,则每次add调用线程都必须阻塞。

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

于 2014-01-25T18:27:42.147 回答
1

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

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

t2.start();
t2.join();
于 2014-01-25T18:47:02.403 回答
0

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

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

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

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

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

与此类似的事情可能是您的测试中发生的事情。

于 2015-04-01T03:33:18.557 回答
-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();
    }
}
于 2014-01-25T18:35:17.047 回答