7

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.

10
  • 6
    'volatile' - the keyword that keeps on giving! Commented Jun 30, 2014 at 14:11
  • 3
    It seems to me you probably don't understand something very well yourself if you can't directly address a very specific quote in Java documentation. In any case a snarky comment with no attempted advice isn't great use of stack overflow's servers, now is it? If the answer is "it's too complex to explain but here are good references." then that is completely fine. Commented Jun 30, 2014 at 14:18
  • 3
    @Holger I suspect there is a short enough answer out there that can address Q1. Q2 is also likely to be extremely short (i.e. "Yes, that's correct".) Commented Jun 30, 2014 at 14:18
  • 1
    @w00te Q1 likely answered by Example of a memory consistency error when using volatile keyword? Commented Jun 30, 2014 at 14:20
  • 1
    @w00te: Ok, so if you wish to hear it: “it's too complex to explain but there are good references.” If you tried to research by yourself, you already knew that. However, the often cited “Java Concurrency in Practice” by Brian Goetz, Joshua Bloch, Doug Lea, et al. is one of the best resources… But normally, asking for such literature is considered off-topic on SO. Commented Jun 30, 2014 at 14:36

6 Answers 6

4

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).

Sign up to request clarification or add additional context in comments.

4 Comments

you mean to say that if I use volatile , the values of x and y read by thread-3 and thread-4 can never be 0?
@TheLostMind: They can not be 0 at the same time. With sequential consistency, the valid results are 0,1, 1,0 and 1,1. With only happens-before, 0,0 would also be a valid result.
down vote: Why does the first code snippet print the only one of the two lines? I don't understand why. I imagine the following scenario: 1) thread 1 writes x to 1. 2) thread 2 writes y to 1. 3) thread 1 executes the if statement(false). 4) thread 2 execute the if statement(false). So, get no result. What's wrong with my comment?
@inherithandle: With only one line I mean not more than one line. Of course you are right, the program can print nothing at all. However, the really interesting case is whether the program can print both lines in one run.
1

The usual example:

while(!condition)
    sleep(10);

if condition is volatile, this behaves as expected. If it is not, the compiler is allowed to optimize this to

if(!condition)
    for(;;)
        sleep(10);

This is completely orthogonal to atomicity: if condition is of a hypothetical integer type that is not atomic, then the sequence

thread 1 writes upper half to 0
thread 2 reads upper half (0)
thread 2 reads lower half (0)
thread 1 writes lower half (1)

can happen while the variable is updated from a nonzero value that just happens to have a lower half of zero to a nonzero value that has an upper half of zero; in this case, thread 2 reads the variable as zero. The volatile keyword in this case makes sure that thread 2 really reads the variable instead of using its local copy, but it does not affect timing.

Third, atomicity does not protect against

thread 1 reads value (0)
thread 2 reads value (0)
thread 1 writes incremented value (1)
thread 2 writes incremented value (1)

One of the best ways to use atomic volatile variables are the read and write counters of a ring buffer:

thread 1 looks at read pointer, calculates free space
thread 1 fills free space with data
thread 1 updates write pointer (which is `volatile`, so the side effects of filling the free space are also committed before)
thread 2 looks at write pointer, calculates amount of data received
...

Here, no lock is needed to synchronize the threads, atomicity guarantees that the read and write pointers will always be accessed consistently and volatile enforces the necessary ordering.

8 Comments

I didn't really understand - are you stating that word-tearing is possible with volatile variables?
In principle, yes, because the concepts are unrelated. In practice, the Java VM has rather strong atomicity guarantees on its own, so you will find it difficult to apply volatile to a non-atomic type.
"atomicity does not protect against ..." Did you mean to say, volatile does not protect against...? The example you give looks like the very definition of "atomic." When we say that some operation is "atomic", we mean that other threads can see the state before, or the state after, but they are guaranteed never to see an intermediate/inconsistent/wrong state.
No, my point is that even if the access is atomic, this does not mean that read-modify-write accesses will be as well.
@SimonRichter JLS specifically states "Writes and reads of volatile long and double values are always atomic.". Word tearing cannot occur for any other primitive values or references as well. Hence volatility gives absolute defense against word tearing
|
1

For question 1, the risk is only reduced (and not eliminated) because volatile only applies to a single read/write operation and not more complex operations such as increment, decrement, etc.

For question 2, the effect of volatile is to make changes immediately visible to other threads. As the quoted passage states "this does not eliminate all need to synchronize atomic actions, because memory consistency errors are still possible." Simply because reads are atomic does not mean that they are thread safe. So establishing a happens before relationship is almost a (necessary) side-effect of guaranteeing memory consistency across threads.

Comments

0

Ad 1: With a volatile variable, the variable is always checked against a master copy and all threads see a consistent state. But if you use that volatility variable in a non-atomic operation writing back the result (say a = f(a)) then you might still create a memory inconsistency. That's how I would understand the remark "reduces the risk". A volatile variable is consistent at the time of read, but you still might need to use a synchronize.

Ad 2: I don't know. But: If your definition of "happens before" includes the remark

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.

I would not dare to rely on any other property except that volatile ensures this. What else do you expect from it?!

Comments

0

Assume that you have a CPU with a CPU cache or CPU registers. Independent from your CPU architecture in terms of number of cores it has, volatile does NOT guarantee you a perfect inconsistency. The only way to achieve this is to use synchronized or atomic references with a performance price.

For example you have multiple threads (Thread A & Thread B) working on a shared data. Assume that Thread A wants to update the shared data and it's is started .For performance reasons, Thread A's stack was moved to CPU cache or registers. Then Thread A updated the shared data. But the problem with those places is that actually they don't flush back the updated value to the main memory immediately. This is where inconsistency's offered because up to the flash back operation, Thread B might have wanted to play with the same data, which would have taken it from the main memory - yet unupdated value.

If you use volatile all the operations will be perfomed on the main memory so you don't have a flush back latency. But, this time you may suffer from thread pipeline. In the middle of write operation (composed of number of atomic operations), Thread B may have been executed by the os to perform a read operation and that's it! Thread B will read the unupdated value again. That's why it's said it reduces the risk.

Hope you got it.

Comments

-1

when coming to concurrency, you might want to ensure 2 things:

  • atomic operations: a set of operations is atomic - this is usually achieved with "synchronized" (higher level constructs). Also with volatile for instance for read/write on long and double.

  • visibility: a thread B sees a modification made by a thread A. Even if an operation is atomic, like a write to an int variable, a second thread can still see a non-up-to-date value of the variable, due to processor caches. Putting a variable as volatile ensures that the second thread does see the up-to-date value of that variable. More than that, it ensures that the second thread sees an up-to-date value of ALL the variables written by the first thread before the write to the volatile variable.

1 Comment

Useful hints, but they don't really answer OPs questions.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.