Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does this multithreaded program output 100 instead of the expected 200?

I'm learning multithreading. Can anyone tell why here the output is always 100, even though there are two threads which are doing 100 increments?

public class App {    
    public static int counter = 0;

    public static void process() {    
        Thread thread1 = new Thread(new Runnable() {    
            @Override
            public void run() {
                for (int i = 0; i < 100; ++i) {
                    ++counter;
                }    
            }
        });

        Thread thread2 = new Thread(new Runnable() {    
            @Override
            public void run() {
                for (int i = 0; i < 100; ++i) {
                    ++counter;
                }    
            }
        });

        thread1.start();
        thread2.start();    
    }

    public static void main(String[] args) {    
        process();
        System.out.println(counter);
    }
}

The output is 100.

like image 238
Kunal Malhotra Avatar asked Dec 01 '22 13:12

Kunal Malhotra


2 Answers

You're only starting the threads, not waiting for them to complete before you print the result. When I run your code, the output is 0, not 100.

You can wait for the threads with

thread1.join();
thread2.join();

(at the end of the process() method). When I add those, I get 200 as output. (Note that Thread.join() throws an InterruptedException, so you have to catch or declare this exception.)

But I'm 'lucky' to get 200 as output, since the actual behaviour is undefined as Stephen C notes. The reason why is one of the main pitfalls of multithreading: your code is not thread safe.

Basically: ++counter is shorthand for

  1. read the value of counter
  2. add 1
  3. write the value of counter

If thread B does step 1 while thread A hasn't finished step 3 yet, it will try to write the same result as thread A, so you'll miss an increment.

One of the ways to solve this is using AtomicInteger, e.g.

public static AtomicInteger counter = new AtomicInteger(0);

...

    Thread thread1 = new Thread(new Runnable() {
        @Override
        public void run() {
            for (int i = 0; i < 100; ++i) {
                counter.incrementAndGet();
            }
        }
    });
like image 90
Glorfindel Avatar answered Dec 05 '22 11:12

Glorfindel


Can anyone tell why here the output is always 100, even though there are two threads which are doing 100 increments?

The reason is that you have two threads writing a shared variable and a third reading, all without any synchronization. According to the Java Memory Model, this means that the actual behavior of your example is unspecified.

In reality, your main thread is (probably) printing the output before the second thread starts. (And apparently on some platforms, it prints it before the first one starts. Or maybe, it is seeing a stale value for counter. It is a bit hard to tell. But this is all within the meaning of unspecified)

Apparently, adding join calls before printing the results appears to fix the problem, but I think that is really by luck1. If you changed 100 to a large enough number, I suspect that you would find that incorrect counter values would be printed once again.

Another answer suggests using volatile. This isn't a solution. While a read operation following a write operation on a volatile is guaranteed to give the latest value written, that value may be a value written by another thread. In fact the counter++ expression is an atomic read followed by an atomic write ... but the sequence is not always atomic. If two or more threads do this simultaneously on the same variable, they are liable to lose increments.

The correct solutions to this are to either using an AtomicInteger, or to perform the counter++ operations inside a synchronized block; e.g.

    for (int i = 0; i < 100; ++i) {
        synchronized(App.class) {
            ++counter;
        }
    }

Then it makes no difference that the two threads may or may not be executed in parallel.


1 - What I think happens is that the first thread finishes before the second thread starts. Starting a new thread takes a significant length of time.

like image 42
Stephen C Avatar answered Dec 05 '22 10:12

Stephen C