6
public class MyThread
{
    volatile static int i;

    public static class myT extends Thread
    {
        public void run ()
        {
            int j = 0;
            while(j<1000000){
                i++;
                j++;
            }
        }
    }

    public static void main (String[] argv)
    throws InterruptedException{
            i = 0;

            Thread my1 = new myT();
            Thread my2 = new myT();
            my1.start();
            my2.start();

            my1.join();
            my2.join();

            System.out.println("i = "+i);
    }
}

Since volatile builds happens-before relationship, the final value of i should be strictly 2000000. However, the actual result is nothing different from being without volatile for variable i. Can anyone explanation why it doesn't work here? Since i is declared volatile, it should be protected from memory inconsistency.

OneZero
  • 11,556
  • 15
  • 55
  • 92

1 Answers1

10

Can anyone explanation why it doesn't work here? Since i is declared volatile, it should be protected from memory inconsistency.

It is protected but unfortunately i++ is not an atomic operation. It is actually read/increment/store. So volatile is not going to save you from the race conditions between threads. You might get the following order of operations from your program:

  1. thread #1 reads i, gets 10
  2. right afterwards, thread #2 reads i, gets 10
  3. thread #1 increments i to 11
  4. thread #2 increments i to 11
  5. thread #1 stores 11 to i
  6. thread #2 stores 11 to i

As you can see, even though 2 increments have happened and the value has been properly synchronized between threads, the race condition means the value only went up by 1. See this nice looking explanation. Here's another good answer: Is a volatile int in Java thread-safe?

What you should be using are AtomicInteger which allows you to safely increment from multiple threads.

static final AtomicInteger i = new AtomicInteger(0);
...
        for (int j = 0; j<1000000; j++) {
            i.incrementAndGet();
        }
Community
  • 1
  • 1
Gray
  • 115,027
  • 24
  • 293
  • 354
  • 1
    Only `i` needs to be an `AtomicInteger`; `j` is purely local to the thread. – Mel Nicholson Apr 29 '13 at 23:44
  • Then what's the point of declaring i as volatile? Since it's not protected anyway. – OneZero Apr 29 '13 at 23:45
  • 1
    I'm not sure how to answer @OneZero. Declaring it as `volatile` won't work because ++ is not atomic. You could synchronize on it every time you update it or use `AtomicInteger`. Making it `volatile` isn't enough. – Gray Apr 29 '13 at 23:46
  • 2
    @OneZero http://docs.oracle.com/javase/tutorial/essential/concurrency/atomic.html should explain a bit about what `volitile` is for. Not this. – Mel Nicholson Apr 29 '13 at 23:47
  • What's the difference between a volatile read and a regular read? Just for the single read step. Aren't they just the same thing? – OneZero Apr 29 '13 at 23:52
  • A `volatile` read crosses a memory barrier and guarantees that you get the most up-to-date value. A "normal" read has no such guarantees. See here: http://jeremymanson.blogspot.com/2007/08/volatile-does-not-mean-atomic.html – Gray Apr 29 '13 at 23:54
  • 1
    @OneZero a non-volitile read can get some bits for the pre-write value, and others from a post-write value, but only for types larger than a `word` on the underlying memory. `double` is the most common offender. Generally speaking, most concurrency issues are too much for `volatile` so look at `synchronize` and `wait` instead. – Mel Nicholson Apr 29 '13 at 23:55
  • 1
    @Gray In practice on anything build this millenium, probably true. I don't think the guarantee is in the language spec, though. Perhaps a more practical guarantee is that write-order is preserved, so if you have a `double data` and a `volitile boolean ready` and code that sets `data` first and `ready = true` second, it is certain that after you see `ready` turn to true, `data` will also have been set, even in a different thread. This is not guaranteed without the `volatile` keyword, where you can see `ready` true and then the value of `data` may not have propagated from the write thread. – Mel Nicholson Apr 30 '13 at 00:03
  • I think for ARM (ie, smartphones), volatile reads are significantly more expensive than non-volatile reads (they're pretty close on x86), so presumably they're not the same. – yshavit Apr 30 '13 at 03:11
  • Certainly @yshavit. Typically `volatile` reads and writes are 1 to 2 orders of magnitude slower because of the memory synchronization and optimization impact. – Gray Apr 30 '13 at 12:33
  • 1
    @Gray From what I understand, on x86, a volatile read is almost exactly as cheap as a non-volatile -- it's the writes that cost you. I haven't tested that myself, but I read it in reputable places such as the java concurrency-interest list. On ARM, I think it's the opposite -- writes are cheap, but reads are expensive. – yshavit Apr 30 '13 at 13:22
  • That is not true @yshavit. I've done performance tests. 1 to 2 orders of magnitude slower. You _always_ pay for the memory synchronization. – Gray Apr 30 '13 at 13:25
  • @Gray They get much worse under heavy contention -- maybe that's what you saw? E.g. http://brooker.co.za/blog/2012/09/10/volatile.html – yshavit Apr 30 '13 at 14:26