8

I have experience this weird behavior of volatile keyword recently. As far as i know,

  1. volatile keyword is applied on to the variable to reflect the changes done on the data of the variable by one thread onto the other thread.

  2. volatile keyword prevents caching of the data on the thread.

I did a small test........

  1. I used an integer variable named count, and used volatile keyword on it.

  2. Then made 2 different threads to increment the variable value to 10000, so the end resultant should be 20000.

  3. But thats not the case always, with volatile keyword i am getting not getting 20000 consistently, but 18534, 15000, etc.... and sometimes 20000.

  4. But while i used synchronized keyword, it just worked fine, why....??

Can anyone please explain me this behaviour of volatile keyword.

i am posting my code with volatile keyword and as well as the one with synchronzied keyword.

The following code below behaves inconsistently with volatile keyword on variable count

public class SynVsVol implements Runnable{

    volatile int  count = 0;

    public void go(){

        for (int i=0 ; i<10000 ; i++){
             count = count + 1;
         }
    }

    @Override
    public void run() {
        go();
    }

    public static void main(String[] args){

        SynVsVol s = new SynVsVol();
        Thread t1 = new Thread(s);
        Thread t2 = new Thread(s);
        t1.start();
        t2.start();

        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("Total Count Value: "+s.count);
    }
}

The following code behaves perfectly with synchronized keyword on the method go().

public class SynVsVol implements Runnable{

    int  count = 0;

    public synchronized void go(){
        for (int i=0 ; i<10000 ; i++){
             count = count + 1;
         }
    }

    @Override
    public void run() {
        go();
    }

    public static void main(String[] args){

        SynVsVol s = new SynVsVol();
        Thread t1 = new Thread(s);
        Thread t2 = new Thread(s);
        t1.start();
        t2.start();

        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("Total Count Value: "+s.count);
    }
}
Marko Topolnik
  • 195,646
  • 29
  • 319
  • 436
Kumar Vivek Mitra
  • 33,294
  • 6
  • 48
  • 75
  • possible duplicate of [Difference between synchronization of field reads and volatile](http://stackoverflow.com/questions/3103204/difference-between-synchronization-of-field-reads-and-volatile) – Mat Jul 01 '12 at 10:53
  • Imagine one thread is switched out during the `count = count + 1` instruction - specifically after it picks up `count` and before it stores `count + 1`. All you need to see is this happening a few thousand times and you're done. Use an `AtomicInteger` to fix this. – OldCurmudgeon Jul 01 '12 at 10:56

4 Answers4

12

count = count + 1 is not atomic. It has three steps:

  1. read the current value of the variable
  2. increment the value
  3. write the new value back to the variable

These three steps are getting interwoven, resulting in different execution paths, resulting in an incorrect value. Use AtomicInteger.incrementAndGet() instead if you want to avoid the synchronized keyword.

So although the volatile keyword acts pretty much as you described it, that only applies to each seperate operation, not to all three operations collectively.

Community
  • 1
  • 1
Adam
  • 15,537
  • 2
  • 42
  • 63
  • Yup. As I understand it, `volatile` implies atomic reads and writes, but not e.g. compare-and-set. – Louis Wasserman Jul 01 '12 at 11:41
  • @Louis Actually atomic reads/writes (not the same as visibility guarantees!) of pointers and primitives up to int size are guaranteed by the JVM. The additional guarantees wrt atomicity are only necessary for double/longs – Voo Jul 01 '12 at 11:53
  • Sorry, yes. Atomicity and visibility. – Louis Wasserman Jul 01 '12 at 12:02
7

The volatile keyword is not a synchronization primitive. It merely prevents caching of the value on the thread, but it does not prevent two threads from modifying the same value and writing it back concurrently.

Let's say two threads come to the point when they need to increment the counter, which is now set to 5. Both threads see 5, make 6 out of it, and write it back into the counter. If the counter were not volatile, both threads could have assumed that they know the value is 6, and skip the next read. However, it's volatile, so they both would read 6 back, and continue incrementing. Since the threads are not going in lock-step, you may see a value different from 10000 in the output, but there's virtually no chance that you would see 20000.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • The volatile keyword is not a synchronization primitive. But it is usually used for synchronization purpose. –  Jul 01 '12 at 11:18
  • 1
    I don't think this is true. volatile acts as though it is enclosed in a synchronized block, synchronized on itself. – GETah Jul 01 '12 at 11:36
  • It is usually used for communication with hardware, (or at least, that's what I use it for), when a compiler cannot know that an '&UxRBR' location can 'magically' have a new value pop into it without being explicitly written by another software instruction. I could probably avoid even that use by moving all such declarations to a separate file so that the compiler cannot optimize the access away anyway. As DBL says, even for interrupt-handlers, using 'volatile' doesn't mean I don't have to take care with, say, the order of incrementing ring buffer pointers. – Martin James Jul 01 '12 at 11:39
  • @Getah, well yeah, according to [the page I linked to in my answer](http://www.javamex.com/tutorials/synchronization_volatile.shtml), `"Access to the variable acts as though it is enclosed in a synchronized block, synchronized on itself."`. So it **does** synchronize, but only a single operation; not the collective three operations (read, increment, write) required to increment the variable. – Adam Jul 01 '12 at 11:40
  • @codesparkle Your first sentence does not say that `The volatile keyword is not a synchronization primitive` :) You should may be change it as `volatile` is indeed a synchronization primitive :) – GETah Jul 01 '12 at 11:42
  • huh? are you referring to dasblinkenlight's answer or mine? ;) – Adam Jul 01 '12 at 11:46
  • 1
    volatile only makes visibility guarantees but does not synchronization whatsoever. Depending on the actual CPU we may very well have thousands of reads at the same time without problems. – Voo Jul 01 '12 at 11:46
  • I don't understand - in the case or read/modify/write, how can a single machine instruction not be atomic anyway - it cannot be interrupted until it is over, so 'synchronize, but only a single operation' is guaranteed anyway? – Martin James Jul 01 '12 at 11:46
  • @Martin "Because the ISA says so" is really the only right answer there. But in practice: Because the CPU (x86) splits up larger ops like `add [r8], 1` into several µops and executes those sequentially. To guarantee atomicity you have to use the lock prefix which is basically what the volatile on a variable amounts to on x86. – Voo Jul 01 '12 at 11:51
  • @GETah I think that it is fair to say that `volatile` is not a synchronization primitive, because operations on volatile variables never block the executing thread. "Being synchronized on itself" part that is often used in explaining the feature is just for illustration purposes: no actual lock is involved. – Sergey Kalinichenko Jul 01 '12 at 11:52
4

The fact that a variable is volatile does not mean every operation it's involved in is atomic. For instance, this line in SynVsVol.Go:

count = count + 1;

will first have count read, then incremented, and the result will then be written back. If some other thread will execute it at the same time, the results depend on the interleaving of the commands.

Now, when you add the syncronized, SynVsVol.Go executes atomically. Namely, the increment is done as a whole by a single thread, and the other one can't modify count until it is done.

Lastly, caching of member variables that are only modified within a syncronized block is much easier. The compiler can read their value when the monitor is acquired, cache it in a register, have all changes done on that register, and eventually flush it back to the main memory when the monitor is released. This is also the case when you call wait in a synchronized block, and when some other thread notifys you: cached member variables will be synchronized, and your program will remain coherent. That's guaranteed even if the member variable is not declared as volatile:

Synchronization ensures that memory writes by a thread before or during a synchronized block are made visible in a predictable manner to other threads which synchronize on the same monitor.

Eran
  • 21,632
  • 6
  • 56
  • 89
3

Your code is broken because it treats the read-and-increment operation on a volatile as atomic, which it is not. The code doesn't contain a data race, but it does contain a race condition on the int.

Marko Topolnik
  • 195,646
  • 29
  • 319
  • 436