2

When I use 2 threads to call a method on the same object (new Counter()) the while loop runs more than the loop limit. Can someone explain the reason for the same in a simple language.

Main Class :

public class MainClass {

public static void main(String[] args) {

    Counter c1 = new Counter();

    MyRunnable r1 = new MyRunnable(c1);

    Thread t1 = new Thread(r1, "t1");
    Thread t2 = new Thread(r1, "t2");
    t1.start();
    t2.start();

}

}

Runnable :

public class MyRunnable implements Runnable {

Counter counter;

public MyRunnable(Counter counter) {
    this.counter = counter;
}

@Override
public void run() {
    counter.add();
}

}

Counter Class :

public class Counter {
    int i = 1;

    public void add() {

        while (i <= 5) {
            System.out.println(i + " " + Thread.currentThread().getName());
            i++;
        }

    }

}

OUTPUT :

1 t1
1 t2
2 t1
3 t2
4 t1
5 t2

I was wondering, if the limit is 5, then why is it outputting 6 values. Sometimes it also outputs 5 values? Would be great if someone sheds some light on this. thanks.

Michael Shopsin
  • 2,055
  • 2
  • 24
  • 43
rattlehead
  • 57
  • 9
  • 1
    It's more useful in multithreading scenarios to think of the loop having an "exit condition", rather than a "limit". The exit condition is that, at the time of evaluation, i>5. However, when more than one thread are inspecting and/or modifying i, It's entirely possible that two threads can read the value of i before any other thread has changed the value. That is in fact what has happened in this case. – Gus Aug 26 '19 at 16:12
  • 1
    you're modifying shared state in an unsafe way. ++ is not atomic. – Nathan Hughes Aug 26 '19 at 16:14
  • 2
    Possible duplicate of [Increments and decrements in multi threaded environment](https://stackoverflow.com/questions/50796366/increments-and-decrements-in-multi-threaded-environment). Multithreading/concurrency is a _hard_ problem, and one poorly understood by many programmers. [You want `AtomicInteger`](https://stackoverflow.com/questions/4818699/practical-uses-for-atomicinteger) for this. – Clockwork-Muse Aug 26 '19 at 16:18
  • 1
    Using an atomic class also takes care of memory visibility issues. – Nathan Hughes Aug 26 '19 at 16:22
  • Gus and Nathan Hughes thank you so much for that explanation. Got it now. :) – rattlehead Aug 26 '19 at 16:22

4 Answers4

3

There are two concurrency issues with your code.

  1. The shared int i is neither appropriately declared nor accessed for concurrent tasks. It need to be at least volatile but in order to correctly handle an atomic increment should be an AtomicInteger. Volatile would help guarantee that other threads can see if the value changes (which is not otherwise guaranteed by the JVM), but atomic integer goes one further and provides atomic operations which is what you really need here.

  2. Your loop can be accessed by both threads simultaneously since the condition check is separate from the increment of the variable. To correctly handle concurrent threads running your loop you need the while loop condition here to also do the increment as a single atomic operation. Either that or you need to add a synchronized block.

Maybe try something like the below untested code:

public class Counter {
    private final AtomicInteger i = new AtomicInteger(1);

    public void add() {
        for (;;)
        {
            int localCount = i.getAndIncrement();
            if (localCount > 5) { break; }

            System.out.println(localCount + " " + Thread.currentThread().getName());
        }
    }
}

To explain the above code line by line:

  • We switched i to an AtomicInteger since it allows us to get the current value and atomically increment the next value by one. This atomicness is important since otherwise two competing threads could increment to the same value instead of reading and incrementing to each value exactly once across all threads. We could achieve the same by putting the incrementing portion of the code in a synchronized block.
  • We switched to a for (;;) loop so that we can move the loop control / termination code to within the loop body. This allows us to easily use the result of the atomic increment both for control code and for usage within the body of the loop (such as in the println statement).
  • i.getAndIncrement() atomically increments our counter and returns to us the value before incrementing. This operation is guaranteed to work across however many threads are operating and ensures we will not miss or duplicate any numbers. Assigning the previous count value to a localCount int provides us the value to use within our loop. If we were to try to re-access i within our loop instead of using localCount, we would end up with a different number at some point since other threads are concurrently incrementing i.
  • We check localCount to see if it has exceed our termination limit, and if it has we break our loop. We don't check i since it may have been modified already by another thread, and we are only interested in whether our current number that this thread has exceeds the limit or not.
  • Lastly we do our println using localCount instead of i, since i has probably been modified by another thread by the time the code gets here (which would result in printing duplicate lines).

One note, this code does not guarantee that the numbers of the counter will be printed in order. It is likely that most of the time that could be the case, but it is certainly not guaranteed and should not be depended on. Your question did not state the actual expected / required results, and if this is a requirement then you most likely need to synchronize around both the increment and println statements to ensure no out of order execution.

e.g. synchronized solution:

public class Counter {
    int i = 1;

    public void add() {
        for (;;) {
            synchronized(this) {
                if (i > 5) { break; }
                System.out.println(i + " " + Thread.currentThread().getName());
                i++;
            }
        }
    }
}
Trevor Freeman
  • 7,112
  • 2
  • 21
  • 40
  • The number of values printed is limited to 4 now on fairly multiple runs. 3 t2 3 t1 4 t2 5 t1 – rattlehead Aug 26 '19 at 17:04
  • 1
    @user3755885 I sort of ignored the fact that the println would not print unique values (assumed your printing was for debugging). Updated to handle that if that is your requirement. – Trevor Freeman Aug 26 '19 at 21:35
0

This is an easy one.

  1. You created a Counter called c1;
  2. You made a Runnable from this counter;
  3. You passed this same runnable to two threads. Two threads will now be operating on the same Counter object c1;
  4. You run the while-loop inside the add() method.

The add() method prints something on one line and on next line it increments i.


In the case of your current output, after the first thread executed its print statement, it got interrupted and second thread got the control of CPU. The second thread managed to execute its print. And then either of the threads incremented the counter (we can't know which one) and the two steps of printing and counter increment continued.

displayName
  • 13,888
  • 8
  • 60
  • 75
-1

What you have just discovered is a race-condition (https://en.m.wikipedia.org/wiki/Race_condition) The threads can access the counter-object without any restrictions. Even if i is an int, there is no guarantee that, for example, 't1' is finished incrementing the value when 't2' reads it. Try running the program a couple of times, the output will probably differ.

To solve your problem, you need to implement a sort of lock, or make use of the synchronized keyword. In general, when implementing an application that makes use of multi-threading, you need to identify critical sections and ensure that race-conditions are avoided.

edit: I cannot believe I wrote protected instead of synchronized, no idea how this happened. Stumbled upon this just today, editing so nobody else gets misinformed.

3ch0
  • 173
  • 1
  • 7
  • Cool ! Does it also mean that the number of values could have been more than 6 too depending on the processing time of each thread ? – rattlehead Aug 26 '19 at 16:34
  • 2
    `protected` is an access-modifier, it doesn't lock the variable. So the race condition should still occur. – Mark Aug 26 '19 at 16:36
-1

The problem is that you are accessing to the variable i concurrently. Java caches the variables in the CPU cache. Since every Thread can be executed in different cores the value of i could be different in each Thread. Even when you are executing i++ the underlaying code is i = i + 1. You are reading the the variable, then you are writing, so multiple Threads could have different values too in that situation.

The solution to this problem is to use Atomic variables like AtomicInteger in your case. These atomic variable are thread-safe and read and writes like in a transaction, blocking the access to thread when the variable is being updated.

In your case instead of declare a int you could declare

    private volatile AtomicInteger atomicInteger = new AtomicInteger(1);

And instead of using i++ you could use atomicInteger.addAndGet(1)

Ezequiel
  • 3,477
  • 1
  • 20
  • 28