Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is this multi-threading code guaranteed to always return zero?

I am taking a book to do some mock test, I have found this question:

import java.util.concurrent.atomic.AtomicInteger;

class AtomicVariableTest {

    private static AtomicInteger counter = new AtomicInteger(0);

    static class Decrementer extends Thread {

        public void run() {
            counter.decrementAndGet(); // #1
        }
    }

    static class Incrementer extends Thread {

        public void run() {
            counter.incrementAndGet(); // #2
        }
    }

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            new Incrementer().start();
            new Decrementer().start();
        }
        System.out.println(counter);
    }
}

The answer:

This program will always print 0.

But I think there is no guarantee that the threads will have completed when it prints the counter value.

I mean, most of the time it will return 0, but if you are strict with the theory there is no guarantee of this.

Am I correct?

like image 955
Daniel Hernández Avatar asked Dec 25 '22 14:12

Daniel Hernández


1 Answers

There is guaranteed. And there is not guaranteed. There is no middle ground.

In this case there is no guarantee that the result is always zero. This is because the threads are not joined - and might never even have actually ran before the print!

For example, among other permutations, the sequence this code could have executed is:

counter.decrementAndGet();    // #1
System.out.println(counter);  // Main thread
counter.incrementAndGet();    // #2
// (and so on, at some arbitrary point after the print)

Avoiding such undesired interleaving/execution is handled in Java (as per the JLS) under happens-before relationships.

If the threads were joined (to the thread with the print) then a happens-before would have been established - in this case that would mean that the threads started and finished running - and the result would be guarantee to be zero.

public static void main(String[] args) {
    final List<Thread> threads = new ArrayList<>();
    for (int i = 0; i < 5; i++) {
        final new Incrementer i = new Incrementer();
        threads.add(i);
        i.start();
        final new Decrementer d = new Decrementer();
        threads.add(d);
        d.start();
    }
    for (final Thread t : threads) { t.join(); }
    System.out.println(counter);
}

See one of the many duplicates: Wait until child threads completed : Java

And this is why you use the ExecutorService or ExecutorCompletionService and never deal with thread management manually because it is extremely error prone otherwise.

like image 101
user2864740 Avatar answered Jan 25 '23 03:01

user2864740