Search code examples
javamultithreadingvolatile

The volatile key word and memory consistency errors


In the oracle Java documentation located here, the following is said:

Atomic actions cannot be interleaved, so they can be used without fear of thread interference. However, this does not eliminate all need to synchronize atomic actions, because memory consistency errors are still possible. Using volatile variables reduces the risk of memory consistency errors, because any write to a volatile variable establishes a happens-before relationship with subsequent reads of that same variable. This means that changes to a volatile variable are always visible to other threads. What's more, it also means that when a thread reads a volatile variable, it sees not just the latest change to the volatile, but also the side effects of the code that led up the change.

It also says:

  • Reads and writes are atomic for reference variables and for most primitive variables (all types except long and double).
  • Reads and writes are atomic for all variables declared volatile (including long and double variables).

I have two questions regarding these statements:

  1. "Using volatile variables reduces the risk of memory consistency errors" - What do they mean by "reduces the risk", and how is a memory consistency error still possible when using volatile?

  2. Would it be true to say that the only effect of placing volatile on a non-double, non-long primitive is to enable the "happens-before" relationship with subsequent reads from other threads? I ask this since it seems that those variables already have atomic reads.


Solution

  • What do they mean by "reduces the risk"?

    Atomicity is one issue addressed by the Java Memory Model. However, more important than Atomicity are the following issues:

    • memory architecture, e.g. impact of CPU caches on read and write operations
    • CPU optimizations, e.g. reordering of loads and stores
    • compiler optimizations, e.g. added and removed loads and stores

    The following listing contains a frequently used example. The operations on x and y are atomic. Still, the program can print both lines.

    int x = 0, y = 0;
    
    // thread 1
    x = 1
    if (y == 0) System.out.println("foo");
    
    // thread 2
    y = 1
    if (x == 0) System.out.println("bar");
    

    However, if you declare x and y as volatile, only one of the two lines can be printed.


    How is a memory consistency error still possible when using volatile?

    The following example uses volatile. However, updates might still get lost.

    volatile int x = 0;
    
    // thread 1
    x += 1;
    
    // thread 2
    x += 1;
    

    Would it be true to say that the only effect of placing volatile on a non-double, non-long primitive is to enable the "happens-before" relationship with subsequent reads from other threads?

    Happens-before is often misunderstood. The consistency model defined by happens-before is weak and difficult to use correctly. This can be demonstrated with the following example, that is known as Independent Reads of Independent Writes (IRIW):

    volatile int x = 0, y = 0;
    
    // thread 1
    x = 1;
    
    // thread 2
    y = 1;
    
    // thread 3
    if (x == 1) System.out.println(y);
    
    // thread 4
    if (y == 1) System.out.println(x);
    

    Only with happens-before, two 0s would be valid result. However, that's apparently counter-intuitive. For that reason, Java provides a stricter consistency model, that forbids this relativity issue, and that is known as sequential consistency. You can find it in sections §17.4.3 and §17.4.5 of the Java Language Specification. The most important part is:

    A program is correctly synchronized if and only if all sequentially consistent executions are free of data races. If a program is correctly synchronized, then all executions of the program will appear to be sequentially consistent (§17.4.3).

    That means, volatile gives you more than happens-before. It gives you sequential consistency if used for all conflicting accesses (§17.4.3).