Reputation: 913
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.
Upvotes: 0
Views: 2017
Reputation: 4699
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 Runnable
s 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.
Upvotes: 2
Reputation: 27115
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:
List<Thread>
.t.join()
for each thread t
in the list. The t.join()
function waits for the thread t
to finish its job.Upvotes: 2
Reputation: 1168
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;
}
}
Upvotes: 1
Reputation: 7938
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.
Upvotes: 2