简体   繁体   中英

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.

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 , eg

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

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 luck 1 . 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; eg

    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.

In Your case, There are three threads are going to execute: one main, thread1 and thread2. All these three threads are not synchronised and in this case Poor counter variable behaviour will not be specific and particular. These kind of Problem called as Race Conditions .

Case1: If i add only one simple print statement before counter print like:

process();
    System.out.println("counter value:");
    System.out.println(counter);

in this situation scenario will be different. and there are lot more.. So in these type of cases, according to your requirement modification will happen.

  1. If you want to execute one thread at time go for Thread join like:

    thread1.join();

    thread2.join();

join() is a Thread class method and non static method so it will always apply on thread object so apply join after thread start.

If you want to read about Multi threading in java please follow; https://docs.oracle.com/cd/E19455-01/806-5257/6je9h032e/index.html

You are checking the result before threads are done.

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


try{
thread1.join();
thread2.join();
}
catch(InterruptedException e){}

And make counter variable volatile .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM