简体   繁体   中英

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

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.

Generally speaking, this is due to the way the Java memory model works. You are accessing shared variables in two distinct threads without synchronization. Neither is the variable declared volatile, nor are you running atomar operations. The absence of coordination and atomar or volatile variables will lead to internal optimizations of the threaded code done by the JVM when executing. Also, non-volatile variables that have not crossed the memory barrier (ie synchronized ) will lead to cached values per thread – and thus two conflicting thread-local copies inside their caches.

Given the absence of a sequential consistency model in Java, the complex runtime optimizations and the peculiarities of the used JVM and underlying system(single or multi-core, hyperthreading), it's impossible to predict the result deterministically – just because it is violating several multi-threading conventions for the Java language model. Running the exact same code on the same machine might still lead to similar results, but due to effects of thread scheduling, CPU usage of other OS processes etc. they will not likely be exactly the same.

Here are some resources about the JMM: http://www.cs.umd.edu/~pugh/java/memoryModel/

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