Search code examples
javamultithreadingincrementdecrement

Thread unsafe decrementing/incrementing - why mostly positive?


I'm wondering about result of unsafe decrementing/incrementing in java threads, so there is my program:

Main class:

public class Start {

    public static void main(String[] args) {

        int count = 10000000, pos = 0, neg = 0, zero = 0;

        for (int x=0; x<10000; x++) {

            Magic.counter = 0;

            Thread dec = new Thread(new Magic(false, count));
            Thread inc = new Thread(new Magic(true, count));

            dec.start();
            inc.start();

            try {
                inc.join();
                dec.join();
            } catch (InterruptedException e) {
                System.out.println("Error");
            }

            if (Magic.counter == 0)
                zero++;
            else if (Magic.counter > 0)
                pos++;
            else
                neg++;
        }

        System.out.println(Integer.toString(neg) + "\t\t\t" + Integer.toString(pos) + "\t\t\t" + Integer.toString(zero));
    }
}

Threads class:

public class Magic implements Runnable {

    public static int counter = 0;

    private boolean inc;
    private int countTo;

    public Magic(boolean inc, int countTo) {
        this.inc = inc;
        this.countTo = countTo;
    }

    @Override
    public void run() {

        for (int i=0;i<this.countTo;i++) {

            if (this.inc)
                Magic.counter++;
            else
                Magic.counter--;
        }

    }
}

I have run program few times, and always getting much more positive result then negative. I have also tried to change order of which threads starts but this changed nothing. Some results:

Number of results < 0 | Number of results > 0 | Number of results = 0

1103                8893                4
3159                6838                3
2639                7359                2
3240                6755                5
3264                6728                8
2883                7112                5
2973                7021                6
3123                6873                4
2882                7113                5
3098                6896                6

Solution

  • I bet you will see the exact opposite behavior with the following change (that is, reverse the branches without changing anything else):

    if (this.inc)
       Magic.counter--; // note change, and lie about `this.inc`
    else
       Magic.counter++;
    

    If true, what might this indicate about this indicate about the thread interactions?

    Now, for fun, make Magic.counter volatile -- [how] do the results change?

    What about removing volatile and surrounding the if/else with a lock? (A lock ensures a full memory-fence and establishes a critical region. It should always yield perfect results.)

    Happy coding.


    Things to consider:

    1. The code only looks at less than or greater to zero, not overall drift/variation: a +1 or -1 is all it takes to tip the scales. (It might be more useful to expand the data collected.)
    2. It takes ever so slightly longer to execute an "else" branch as a jump is required; normally this is a non-issue, but over 10 million cycles... one or two isn't much.
    3. Lack of volatile/memory-fence leaves for much lee-way in the visibility of the Magic.counter variable. (I believe a conforming JVM could actually yield far worse results...)
    4. The ++ and -- operators are inherently non-atomic.
    5. Thread interleaving is generally "non-deterministic"; less so if executed across multiple cores.