• Post Reply Bookmark Topic Watch Topic
  • New Topic

volatile modifier  RSS feed

 
Akshith Reddy
Greenhorn
Posts: 6
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Sir,Can you please explain me any small program of volatile modifier.I referred many books I understood the theory part but did not get a single program.
 
Mike. J. Thompson
Bartender
Posts: 689
17
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
When you run a program on your PC, at some point the code you've written has to be placed on the CPU to run and the variables your code uses will be made available to the CPU. One approach that could be taken is that every time your code wants to access a variable the CPU can request that the variable contents are loaded from RAM, but loading data from RAM takes a relatively long time so that would affect performance. So what the CPU does is it caches things that it thinks it will need to access a lot. It is much quicker for it to load data from its local cache rather than go to RAM each time. Modern CPU's actually have multiple cores inside them, and each core will have their own cache that other cores don't see. Each time the CPU reads a variable it will take it from the cache (if its there), and each time it writes to that variable it will write it to the cache.

This poses a problem when writing programs with multiple threads though. Each thread may run on a different core, so when one thread writes to a variable there is no guarentee when (or even if) another thread will see the effect of that write. This is where the volatile keyword comes in. When you put the volatile keyword next to the variable this tells Java not to let the contents of that variable to be cached in the CPU cache. It creates a happens-before relationship between writes to that variable and subsequent reads of that variable. Reads of the variable will always come from main memory, and writes to that variable will always go to main memory.

Consider the following class:



Now imagine that a thread (thread1) calls the run() method in NotThreadSafe, and some time later another thread (thread2) calls the stop() method. What will happen? Well the answer is that we can't know. There is no happens-before relationship between the write to keepRunning (in stop()), and subsequent reads of keepRunning (in run()). Thread1 might see that keepRunning is now false and so exit the run() method, or it might not and keep on running.

This is where the volatile keyword comes in:



Just by adding the volatile keyword we have created a happens-before relationship between writes to keepRunning and subsequent reads. This means that when thread2 calls stop() thread1 will see the effect of that call at some point in the future and will stop.

We could have achieved the same thing using the synchronize keyword, or by using a lock from the java.concurrent package. In some situations this is required because the volatile keyword cannot make everything threadsafe, but synchronization and locks will be less perforant.
 
Tushar Goel
Ranch Hand
Posts: 934
4
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Thanks Mike, wonderful explanation.

Just to add to Mike, volatile guarantees visibility and ordering only. It doesn't guarantees mutual exclusion. Synchronized keyword/block and locks guarantees mutual
exclusion as well. So it depends upon your requirement what do you want to achieve..
 
Winston Gutkowski
Bartender
Posts: 10573
65
Eclipse IDE Hibernate Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Mike. J. Thompson wrote:Just by adding the volatile keyword we have created a happens-before relationship between writes to keepRunning and subsequent reads. This means that when thread2 calls stop() thread1 will see the effect of that call at some point in the future and will stop. ... In some situations this is required because the volatile keyword cannot make everything threadsafe, but synchronization and locks will be less perforant.

Like Tushar, cow for the explanation; but I'm not entirely sure that the effect of your code is quite the same as full synchronization - but I could be wrong; especially for a boolean.

I've always taken volatile to mean "read-synchronized", but not necessarily write-synchronized, and I seem to remember examples involving increments, where simply adding volatile wasn't sufficient to ensure correct updates.

TBH, I've yet to find a really good example (or explanation) of "happens before" so, as I say, I could be wrong. And if you know of one, I'd be really grateful if you could post it.

Winston
 
Mike. J. Thompson
Bartender
Posts: 689
17
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Winston Gutkowski wrote:
Like Tushar, cow for the explanation; but I'm not entirely sure that the effect of your code is quite the same as full synchronization - but I could be wrong; especially for a boolean.

I've always taken volatile to mean "read-synchronized", but not necessarily write-synchronized, and I seem to remember examples involving increments, where simply adding volatile wasn't sufficient to ensure correct updates.


The keyword volatile creates a synchronizes-with and a happens-before relationship between writes to a variable and subsequent reads of that variable. From the Java Memory Model (in the oh-so-easy-to-understand* [Java Language Specification, Chapter 17 Threads and Locks):

*Just in case it's not clear, that was sarcasm!

A write to a volatile variable v (§8.3.1.4) synchronizes-with all subsequent reads of v by any thread (where "subsequent" is defined according to the synchronization order).


and

A write to a volatile field (§8.3.1.4) happens-before every subsequent read of that field.


You are correct that just adding volatile will not ensure desired behaviour in all situations. It doesn't guarantee the ordering that instructions are run from different threads (so doesn't provide mutual-exclusion as Tushar mentioned). What it really does is guarantee that actions in one thread are visible in another thread. The Java Memory Model does not require this to be true, so without some form of synchronization (volatile, locks, the synchronized keyword etc) its possible that actions in one thread will never be visible in another thread, or it could appear that operations from another thread ran in a strange order that seems paradoxical.

Winston Gutkowski wrote:TBH, I've yet to find a really good example (or explanation) of "happens before" so, as I say, I could be wrong. And if you know of one, I'd be really grateful if you could post it.


Well I'm not sure I can give a really good example, but I'll do my best! Again, from the Java Language Specification:

Two actions can be ordered by a happens-before relationship. If one action happens-before another, then the first is visible to and ordered before the second.

...

It should be noted that the presence of a happens-before relationship between two actions does not necessarily imply that they have to take place in that order in an implementation. If the reordering produces results consistent with a legal execution, it is not illegal.

For example, the write of a default value to every field of an object constructed by a thread need not happen before the beginning of that thread, as long as no read ever observes that fact.


So, the happens-before relationship puts a constraint on the set of allowed 'implementations' of a program, i.e the set of re-orderings that are allowed to be applied to the instructions that will be run. If there is a happens-before relationship between two actions then it is not legal for any part of the program to observe those actions happening out of order. This applies to anything that can re-order the instructions (or do anything that makes it look like a re-ordering has happened, such as caching values), so the Java compiler, the Just-In-Time compiler, and the processor (that can also re-order instructions if it wants to, or can cache values which may make it look like a re-ordering occurred).

So in my example above the happens-before relationship that volatile places on writes to and subsequent reads of keepRunning mean that the value is not allowed to be cached. My original explanation talked about caches by the CPU, but there are more ways for it to be cached than that. The Java memory model only requires non-synchronized (in the general meaning of synchronized, not the keyword) code to have consistent ordering for intra-thread actions (so for actions within the same thread). So if you have two reads of a variable with no intermediate writes of that variable in the same thread then it is perfectly allowed for Java to remember the value used from the first read and use that for the second. So in my program above the while-loop could loop forever simply because Java does not bother to read the value each time round the loop (since a single-threaded implementation could not possibly change the value while the loop is running if you assume that the contents of the loop don't change the value).
 
Stephan van Hulst
Saloon Keeper
Posts: 7817
142
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
A good rule of thumb: Only use volatile for simple signalling. This means one single assignment to signal, and one single read to check the signal. Anything more than that, and you probably need mutual exclusion.
 
Jesper de Jong
Java Cowboy
Sheriff
Posts: 16028
87
Android IntelliJ IDE Java Scala Spring
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
If you really want to learn everything about concurrency, synchronization and multi-threading in Java, then I highly recommend the book Java Concurrency in Practice.

It explains exactly what a "happens-before relationship" is that Mike mentioned.
 
Winston Gutkowski
Bartender
Posts: 10573
65
Eclipse IDE Hibernate Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Mike. J. Thompson wrote:You are correct that just adding volatile will not ensure desired behaviour in all situations. It doesn't guarantee the ordering that instructions are run from different threads (so doesn't provide mutual-exclusion as Tushar mentioned).

And this is the bit that confuses me.

I understand that the compiler is allowed to reorder instructions providing they produce the same outcome, and that volatile restricts this so that (I presume) every read and write done to such a variable is done in sequence for a particular Thread; but how does that help unless other Threads are guaranteed to see the result of the latest update done by any Thread?

I understand that order between Threads can't be guaranteed, but isn't this also true of synchronization? For any given race-condition you can't predict which Thread will acquire the lock first and, once released, you can't predict which Thread in the queue (if there is one) will acquire it next.

However, I do sort of see from your explanation how volatility might not work with incrementing since, without exclusion, you can't guarantee that another Thread doesn't update the value between "our" Thread reading and writing it.
I also assume that it might be possible for a Thread to see a volatile long in an "unfinished" state, since updates to longs aren't atomic.

Thanks for all the links though. I'll look 'em over in the next couple of days and see if they don't make things clearer.

Winston
 
Stephan van Hulst
Saloon Keeper
Posts: 7817
142
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Winston Gutkowski wrote:I understand that the compiler is allowed to reorder instructions providing they produce the same outcome, and that volatile restricts this so that (I presume) every read and write done to such a variable is done in sequence for a particular Thread; but how does that help unless other Threads are guaranteed to see the result of the latest update done by any Thread?

I understand that order between Threads can't be guaranteed, but isn't this also true of synchronization? For any given race-condition you can't predict which Thread will acquire the lock first and, once released, you can't predict which Thread in the queue (if there is one) will acquire it next.


This is not what Mike means. He means that volatile does not prevent instructions from different threads (even if they have a happens-before relationship in their *own* thread) to interweave in some way that breaks the program. Even if a thread can see the latest value of a variable (volatile *does* guarantee this), volatile doesn't make 'read-then-write' operations atomic. This is why volatile is only useful when threads perform only one assignment at a time, OR one read.

Mirroring Mike's code:


In this code we require stop to be called twice for the task to actually stop running. Volatile guarantees that when one thread assigns a new value to the stops variable, all other threads will see that update immediately. However, because the ++ operator isn't atomic, one thread may read the value before another writes to it, and then write the incremented value, overwriting the other write operation.
 
Winston Gutkowski
Bartender
Posts: 10573
65
Eclipse IDE Hibernate Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Stephan van Hulst wrote:Volatile guarantees that when one thread assigns a new value to the stops variable, all other threads will see that update immediately. However, because the ++ operator isn't atomic, one thread may read the value before another writes to it, and then write the incremented value, overwriting the other write operation.

OK, that's what I thought. Thanks Stephen.

And what about atomicity? If volatile doesn't make any guarantees about that, then
  volatile long
would seem to me to be a pointless exercise, since any Thread could "see" an unfinished update.
  volatile int
on the other hand makes a lot of sense.

TBH, I forget about the atomicity of reference updates; but presumably the same could apply to them.

Winston

PS: Having a squint at the book Jesper recommended. Seems I should have read it ages ago.
 
Stephan van Hulst
Saloon Keeper
Posts: 7817
142
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Emphasis mine.

The Java® Language Specification wrote:For the purposes of the Java programming language memory model, a single write to a non-volatile long or double value is treated as two separate writes: one to each 32-bit half. This can result in a situation where a thread sees the first 32 bits of a 64-bit value from one write, and the second 32 bits from another write.

Writes and reads of volatile long and double values are always atomic.

Writes to and reads of references are always atomic, regardless of whether they are implemented as 32-bit or 64-bit values.


http://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.7
 
Winston Gutkowski
Bartender
Posts: 10573
65
Eclipse IDE Hibernate Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Stephan van Hulst wrote:Writes and reads of volatile long and double values are always atomic.
Writes to and reads of references are always atomic, regardless of whether they are implemented as 32-bit or 64-bit values.

Bril. Thanks Stephen.

Winston
 
Mike. J. Thompson
Bartender
Posts: 689
17
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Writes to references are atomic, but do note that there is no general happens-before relationship between non-final fields being initialised and the end of the constructor. This means that threads may view references being assigned to variables before the referenced objects are finished being constructed.

This is why double-checked locking never worked.
 
Mike. J. Thompson
Bartender
Posts: 689
17
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Thanks for the book reference Jesper. That book has an impressive list of authors!

I read the memory model chapter at lunch today and I think my explanation above is compatible with it. I particularly liked the way it explained what a partial order is (something missing from the Java Language Specification).

There were several things in that chapter that were new to me to so well worth a read. I think i'll go back and read the rest of the book.
 
Winston Gutkowski
Bartender
Posts: 10573
65
Eclipse IDE Hibernate Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Mike. J. Thompson wrote:Writes to references are atomic, but [...] This is why double-checked locking never worked.

Aha! Verrry interestink. Cheers for that.

Winston
 
  • Post Reply Bookmark Topic Watch Topic
  • New Topic
Boost this thread!