从其他线程更新 AtomicInteger

Update AtomicInteger from other thread

我有一个 class 可以在各自的线程上创建许多新对象,我想在线程中保持 运行 计数。我想要一个 AtomicInteger 但它没有达到我的预期,而是得到了一个较小的版本。我假设这是一个竞争条件错误 - 但我不完全确定。

A 创建了这个测试示例,它重现了我想做的事情。

public class Test {

    public static void main(String args[]) {
        AtomicInteger total = new AtomicInteger(0);
        for (int i = 0; i < 10; i++) {

            DoThing doThing = new DoThing();

            Thread thread = new Thread(doThing);
            thread.start();
            total.addAndGet(doThing.getTally());
        }

        System.out.println(total.get());
    }
}

class DoThing implements Runnable {

    int tally = 0;
    @Override
    public void run() {

        for(int i = 0; i< 100; i++) {
            tally++;
        }

        System.out.println("Tally is " + tally);

    }

    public int getTally() {
        return tally;
    }
}

然而,这输出:

Tally is 100
Tally is 100
Tally is 100
Tally is 100
Tally is 100
Tally is 100
Tally is 100
Tally is 100
0
Tally is 100
Tally is 100

当我希望最终输出为 1000 时。如何跨线程递增?

提前致谢。

是的,存在数据竞争。竞争发生在调用 doThing.getTally() 的主线程和启动的 "worker" 线程之间。看起来每次你的主线程都能够在工作人员有机会进入其 for 循环之前可靠地从每个工作人员那里获得 "tally" 。它甚至可能在工作人员调用其 run() 方法之前发生。

您的主线程需要 join 工作人员:

  • 创建并启动十个工作线程,并将每一个添加到 List<Thread>
  • 然后在单独的循环中,为列表中的每个线程 t 调用 t.join()t.join() 函数等待线程 t 完成其工作。
  • 最后,统计并相加。

在您的示例代码中,total 只能从主线程访问。使其成为 Atomic 不会对结果产生任何影响。您应该将 Atomic 值传递给您的线程并增加其中的值。或者使用LongAdder(自增法)。
在主线程中打印 Atomic 值之前,您必须等待所有线程完成。
如果你想使用低级阻塞,你可以使用 CyclicBarrier 让主线程等待所有线程。

CountDownLatch latch = new CountDownLatch(10);
List<DoThing> things = new ArrayList();
AtomicInteger total = new AtomicInteger(0);
    for (int i = 0; i < 10; i++) {

        DoThing doThing = new DoThing(latch);
        things.add(doThing);
        Thread thread = new Thread(doThing);
        thread.start();
        total.addAndGet(doThing.getTally());
    }
// Wait till all the threads are done.
// Each thread counts the latch down
latch.await()
int total = 0;

// Calculate sum after all the threads are done.
for (DoThing thing: things) {
    total += thing.getTally();
}
System.out.println(total);



class DoThing implements Runnable {

    private CountDownLatch latch;

    public DoThing(CountDownLatch latch) {
        this.latch = latch;
    }

    int tally = 0;
    @Override
    public void run() {

        for(int i = 0; i< 100; i++) {
            tally++;
        }
        latch.countDown();
        System.out.println("Tally is " + tally);

    }

    public int getTally() {
        return tally;
    }
}

试试这个:

public static void main(String args[]) {
    AtomicInteger tally = new AtomicInteger(0);
    List<Thread> threadList = new ArrayList<Thread>();
    for (int i = 0; i < 10; i++) {
        Thread t = new Thread(new DoThing(tally));
        t.start();
        threadList.add(t);
    }
    for (Thread t : threadList) {
        try { t.join(); } catch (Exception e){}
    }
    System.out.println("Total tally: " + tally.get());
}

public static class DoThing implements Runnable {
    private static final Random rand = new Random();
    private final AtomicInteger tally;

    public DoThing(AtomicInteger tally) {
        this.tally = tally;
    }

    @Override public void run() {
        for (int i = 0; i < 100; i++) {
            int currTally  = tally.incrementAndGet();
            System.out.println("Thread " + Thread.currentThread().getName() + ": " + currTally);
            // Random sleep to show that your threads are properly concurrently incrementing
            try { Thread.sleep(rand.nextInt(10)); } catch (Exception e) {}
        }
    }
}

你的问题的根源是你误解了如何使用 AtomicInteger,你把它当作一个普通的 int 对待它,它根本没有被同时访问。

另外 getTally() 是一个竞争条件,直到您通过调用 Thread.join().

确保线程已完成

因此,您可以通过让线程中的所有 Runnable 更新同一个 AtomicInteger 实例来保持 up-to-date 计数,并且您可以通过等待确保总数正确让所有线程在获得计数之前通过 join()ing 完成它们的计数。