0

我有一个类,每个在自己的线程上创建许多新对象,我想保持跨线程的运行计数。我想要一个,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 时。如何跨线程递增?

提前致谢。

4

4 回答 4

2

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

您的主线程需要join工人:

  • 创建并启动十个工作线程,并将每个工作线程添加到List<Thread>.
  • 然后在一个单独的循环中,调用列表中t.join()的每个线程t。该t.join()函数等待线程t完成其工作。
  • 最后,得到计数并将它们相加。
于 2018-02-22T20:04:54.570 回答
2

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

于 2018-02-22T20:06:46.123 回答
2

试试这个:

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因此,您可以通过让线程中的所有 s 更新同一个实例来保持最新的计数,并且您可以通过在获取计数之前AtomicInteger等待所有线程完成它们的计数来确保您拥有正确的总数join().

于 2018-02-22T20:18:10.833 回答
1
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;
    }
}
于 2018-02-22T20:09:03.947 回答