Reputation:
For years and years, I've tried to understand the part of Java specification that deals with memory model and concurrency. I have to admit that I've failed miserably. Yes' I understand about locks and "synchronized" and wait() and notify(). And I can use them just fine, thank you. I even have a vague idea about what "volatile" does. But all of that was not derived from the language spec - rather from general experience.
Here are two sample questions that I am asking. I am not so much interested in particular answers, as I need to understand how the answers are derived from the spec (or may be how I conclude that the spec has no answer).
Upvotes: 37
Views: 7229
Reputation: 34215
JVM Memory model
High level diagram
Code sample
class MainClass {
void method1() { //<- main
int variable1 = 1;
Class1 variable2 = new Class1();
variable2.method2();
}
}
class Class1 {
static Class2 classVariable4 = new Class2();
int instanceVariable5 = 0;
Class2 instanceVariable6 = new Class2();
void method2() {
int variable3 = 3;
}
}
class Class2 { }
*Notes:
thread stack
contains only local variablesheap
even they are primitivesWhat does "volatile" do, exactly?
Are writes to variable atomic? Does it depend on variable's type?
[Java thread safe of local variables]
Upvotes: 2
Reputation: 1
This explains it using cities (threads) and planets (main memory).
http://mollypages.org/tutorials/javamemorymodel.mp
There are no direct flights from city to city.
You have to first go to another planet (Mars in this case) and then to another city on your home planet. So, from NYC to Tokyo, you have to go:
NYC -> Mars -> Tokyo
Now replace NYC and Tokyo with 2 threads, Mars with Main memory and the flights as acquiring/releasing locks and you have the JMM.
Upvotes: 0
Reputation: 5846
I recently found an excellent article that explain volatile as:
First, you have to understand a little something about the Java memory model. I've struggled a bit over the years to explain it briefly and well. As of today, the best way I can think of to describe it is if you imagine it this way:
Each thread in Java takes place in a separate memory space (this is clearly untrue, so bear with me on this one).
You need to use special mechanisms to guarantee that communication happens between these threads, as you would on a message passing system.
Memory writes that happen in one thread can "leak through" and be seen by another thread, but this is by no means guaranteed. Without explicit communication, you can't guarantee which writes get seen by other threads, or even the order in which they get seen.
The Java volatile modifier is an example of a special mechanism to guarantee that communication happens between threads. When one thread writes to a volatile variable, and another thread sees that write, the first thread is telling the second about all of the contents of memory up until it performed the write to that volatile variable.
Additional links: http://jeremymanson.blogspot.com/2008/11/what-volatile-means-in-java.html http://www.javaperformancetuning.com/news/qotm030.shtml
Upvotes: 4
Reputation:
Another attempt to provide a summary of things I understood from the answers here and from other sources (the first attempt was pretty far off base. I hope this one is better).
Java memory model is about propagating values written to memory in one thread to other threads so that other threads can see them as they read from memory.
In short, if you obtain a lock on a mutex, anything written by any thread that released that mutex before will be visible to your thread.
If you read a volatile variable, anything written to that volatile variable before you read it is visible to the reading thread. Also, any write to volatile variable done by the thread that write to your variable before the write to your variable is visible. Moreover, in Java 1.5 any write at all, volatile or not, that happened on any thread that wrote to your volatile variable before the write to your volatile variable will be visible to you.
After an object is constructed, you can pass it to another thread, and all final members will be visible and fully constructed in the new thread. There are no similar guarantees about non-final members. That makes me think that assignment to a final member acts as a write to volatile variable (memory fence).
Anything that a thread wrote before its Runnable exited is visible to the thread that executes join(). Anything that a thread wrote before executing start() will be visible to the spawned thread.
Another thing to mention: volatile variables and synchronization have a function that's rarely mentioned: besides flushing the thread cache and providing one-thread-at-a-time access they also prevent compiler and CPU from reordering reads and writes across sync boundary.
None of it is new and the other answers have stated it better. I just wanted to write this up to clear my head.
Upvotes: 0
Reputation: 3092
Other answers above are absolutely correct in that your question is not for the feint of heart.
However, I understand your pain on really wanting to get what is under the hood - for this I would point you back to the worlds compilers and lower-level predecessors to java - i.e. assembly, C and C++.
Read about different kinds of barriers ('fences'). Understanding what a memory barrier is, and where it is necessary, will help you have an intuitive grasp of what volatile does.
Upvotes: 1
Reputation: 3216
One notion might be helpful: data (datum) and copies.
If you declare a variable, let's say a byte, it resides somewhere in the memory, in a data segment (roughly speaking). There are 8 bits somewhere in the memory devoted to store that piece of information.
However, there can be several copies of that data, moving around in your machine. For various technical reasons, e.g. thread's local storage, compiler optimizations. And if we have several copies, they might be out of sync.
So you should always keep this notion in mind. It's true not only for java class fields, but for cpp variables, database records (the record state data gets copied into several sessions etc.). Variables, their hidden/visible copies and the subtle syncing issues will be around forever.
Upvotes: 0
Reputation: 165232
This is a good link which can give you a little in depth information:
http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html
Upvotes: 4
Reputation: 346270
volatile
variables can be cached thread-locally, so different threads may see different values at the same time; volatile
prevents this (source)long
and double
, though 64bit JVMs probably implement them as atomic operationsUpvotes: 15
Reputation: 1500385
I'm not going to attempt to actually answer your questions here - instead I'll redirect you to the book which I seeing recommended for advice on this topic: Java Concurrency in Practice.
One word of warning: if there are answers here, expect quite a few of them to be wrong. One of the reasons I'm not going to post details is because I'm pretty sure I'd get it wrong in at least some respects. I mean no disrespect whatsoever to the community when I say that the chances of everyone who thinks they can answer this question actually having enough rigour to get it right is practically zero. (Joe Duffy recently found a bit of the .NET memory model that was surprised by. If he can get it wrong, so can mortals like us.)
I will offer some insight on just one aspect, because it's often misunderstood:
There's a difference between volatility and atomicity. People often think that an atomic write is volatile (i.e. you don't need to worry about the memory model if the write is atomic). That's not true.
Volatility is about whether one thread performing a read (logically, in the source code) will "see" changes made by another thread.
Atomicity is about whether there is any chance that if a change is seen, only part of the change will be seen.
For instance, take writing to an integer field. That is guaranteed to be atomic, but not volatile. That means that if we have (starting at foo.x = 0):
Thread 1: foo.x = 257;
Thread 2: int y = foo.x;
It's possible for y
to be 0 or 257. It won't be any other value, (e.g. 256 or 1) due to the atomicity constraint. However, even if you know that in "wall time" the code in thread 2 executed after the code in thread 1, there could be odd caching, memory accesses "moving" etc. Making the variable x
volatile will fix this.
I'll leave the rest up to real honest-to-goodness experts.
Upvotes: 36
Reputation: 615
I wont try to explain these issues here but instead refer you to Brian Goetz excellent book on the subject.
The book is "Java Concurrency in Practice", can be found at Amazon or any other well sorted store for computer literature.
Upvotes: 7