0

I have a class that creates many new objects each on their own thread and I would like to keep a running count across the threads. I wanted to have an AtomicInteger but it's not doing what I expected and instead just gets a smaller version. I'm assuming that this is a race condition error - but I'm not entirely sure.

A created this test example which recreates what I want to do.

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

However, this outputs:

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

When I would like the final output to be 1000. How can I increment across threads?

Thanks in advance.

LivingRobot
  • 883
  • 2
  • 18
  • 34
  • 1
    You need to wait until thread completes its job before calling `doThings.getTally()` – Ivan Feb 22 '18 at 20:10

4 Answers4

2

Yes, there is a data race. The race is between the main thread calling doThing.getTally() and the "worker" thread starting up. Looks like each time your main thread is reliably able to get the "tally" from each worker before the worker even gets a chance to enter its for loop. It could be happening even before the worker calls its run() method.

Your main thread needs to join the workers:

  • Create and start the ten worker threads, and add each one to a List<Thread>.
  • Then in a separate loop, call t.join() for each thread t in the list. The t.join() function waits for the thread t to finish its job.
  • Finally, get the tallies and add them up.
Solomon Slow
  • 25,130
  • 5
  • 37
  • 57
  • join would just run all the threads in a sequence, killing all the multithreading. – ekaerovets Feb 22 '18 at 20:10
  • 1
    @ekaerovets depending on how you do it. You could create an array of threads, start them all and after that iterate through that array and call join on each thread – Ivan Feb 22 '18 at 20:12
  • @ekaerovets, No, because I recommended using two loops. One to start the threads, saving each one in a list, and then a second loop to join them all. – Solomon Slow Feb 22 '18 at 20:14
  • @Ivan yes, makes sense.I didn't think about it. – ekaerovets Feb 22 '18 at 20:15
  • This would work, but it ignores that OPs misunderstanding of how to use an `AtomicInteger`. – xtratic Feb 22 '18 at 20:35
2

In your example code, total is only accessed from main thread. Making it Atomic won't have any impact on the result. You should pass Atomic value to your threads and increase the value in there. Or use LongAdder (increment method).
And you have to wait all threads to finish before printing Atomic value in main thread.
If you want to use low level blocking, you can use CyclicBarrier to make main thread wait all threads.

miskender
  • 7,460
  • 1
  • 19
  • 23
2

Try with this:

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) {}
        }
    }
}

The root of your problem is that you are misunderstanding how to use AtomicInteger, you are treating it just like a normal int and it is not being accessed concurrently at all.

Also getTally() is a race condition until you assure that the thread has completed by calling Thread.join().

So you can keep an up-to-date tally by having all the Runnables in the threads update the same AtomicInteger instance and you can ensure that you have the right total by waiting for all the threads to finish their tallies by join()ing them before getting the tally.

xtratic
  • 4,600
  • 2
  • 14
  • 32
  • Wouldn't it print 0? – ekaerovets Feb 22 '18 at 20:19
  • 1
    Don't you need to wait for all started threads to finish before asking for final result? – Ivan Feb 22 '18 at 20:20
  • Sorry, printing the total tally was a remnant. Yes, you should join all the threads for the final result, but in this example it doesn't matter much since each thread prints whenever it increments the tally. I have added the code to join the threads. – xtratic Feb 22 '18 at 20:24
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;
    }
}
ekaerovets
  • 1,158
  • 10
  • 18
  • 2
    This would be a better answer if you explained it instead of just doing the dude (or dudette)'s homework for him (or, for her). – Solomon Slow Feb 22 '18 at 20:11
  • @jameslarge well, maybe. I thought that the example is self-explanatory, and personally I would prefer a few lines of code instead of explanation. Still, if that's a homework, you're right. – ekaerovets Feb 22 '18 at 20:18
  • Thanks very much! This was not homework. Just working on something. And this was very helpful. – LivingRobot Feb 22 '18 at 20:21