views:

198

answers:

2

Hi! I am wondering at the difference between declaring a variable as volatile and always accessing the variable in a synchronized(this) block in JAVA (particularly J2ME)?

According to this article http://www.javamex.com/tutorials/synchronization_volatile.shtml there is a lot to be said and there are many differences but also some similarities.

I am particularly interested in this piece of info:

...

  • access to a volatile variable never has the potential to block: we're only ever doing a simple read or write, so unlike a synchronized block we will never hold on to any lock;
  • because accessing a volatile variable never holds a lock, it is not suitable for cases where we want to read-update-write as an atomic operation (unless we're prepared to "miss an update");

What do they mean by read-update-write? Doesn't a write also an update or do they simply mean that the update is a write that depends on the read info?

Most of all, when is it more suitable to declare variables volatile than access them through synchronized? Is it a good idea to use volatile for variables that depend on input? For instance, there is a variable called render that is read through the rendering loop and set by a keypress event?

Thanks a lot!

+2  A: 

It's important to understand that there are two aspects to thread safety: (1) Execution sequence (happens-before relationships), and (2) memory visibility. The first has to do with what happens, and when, and the second to do with when the effects in memory of what has been done are visible to other threads. Because each CPU has several levels of cache between it and main memory, threads running on different CPUs or cores can see "memory" differently at any given moment in time because threads are permitted to obtain and work on local copies of main memory.

Using synchronized prevents any other thread from obtaining the monitor (or lock) for the same object, thereby preventing any and all code protected by synchronization on the same object from ever executing concurrently. Importantly, synchronization also creates a memory barrier, causing a memory visibility constraint such that anything that is done after some thread acquires a lock appears to another thread subsequently acquiring the same lock to have happened before the other thread acquired the lock. This causes flushing of the CPU caches when a monitor is acquired and when it is released, which is expensive (relatively speaking).

Volatile, on the other hand, simply forces all accesses (read or write) to the volatile variable to occur to main memory, effectively keeping the volatile variable out of CPU caches. This can be important for some actions where it is simply required that visibility of the variable be correct and order of accesses is not important.

I just yesterday had some code where a shared but immutable object is recreated on the fly, and I needed to update several references to the shared object - volatile is perfect for that situation. I needed the other threads to see the recreated object as soon as it was published, but did not need the additional overhead of full synchronization and it's attendant contention and cache flushing.

Speaking to your read-update-write question, specifically. Consider the following unsafe code:

public void updateCounter() {
    if(counter==1000) { counter=0; }
    else              { counter++; }
    }

Now, with the updateCounter() method unsynchronized, two threads may enter it at the same time. Among the many permutations of what could happen, one is that thread1 does the test for counter==1000 and finds it true and is then suspended. Then thread2 does the same test and also sees it true and is suspended. Then thread1 resumes and sets counter to 0. Then thread2 resumes and again sets counter to 0 because it missed the update from thread1. This can also happen even if thread switching does not occur as I have described, but simply because two different cached copies of counter were present in two different CPU cores and the threads each ran on a separate core. For that matter, one thread could have counter at one value and the other could have counter at some entirely different value just because of caching.

What's important in this example is that the variable counter was read from main memory into cache, updated in cache and written back to main memory at some indeterminate point later when a memory barrier occurred.

Software Monkey
Thanks very much! The example with the counter is simple to understand. However, when things get real, it's a bit different.
Albus Dumbledore
+6  A: 

volatile is a field modifier, while synchronized modifies code blocks and methods. So we can specify three variations of a simple accessor using those two keywords:

     int i1;
     int geti1() {return i1;}

     volatile int i2;
     int geti2() {return i2;}

     int i3;
     synchronized int geti3() {return i3;}

geti1() accesses the value currently stored in i1 in the current thread. Threads can have local copies of variables, and the data does not have to be the same as the data held in other threads. In particular, another thread may have updated i1 in it's thread, but the value in the current thread could be different from that updated value. In fact Java has the idea of a "main" memory, and this is the memory that holds the current "correct" value for variables. Threads can have their own copy of data for variables, and the thread copy can be different from the "main" memory. So in fact, it is possible for the "main" memory to have a value of 1 for i1, for thread1 to have a value of 2 for i1 and for thread2 to have a value of 3 for i1 if thread1 and thread2 have both updated i1 but those updated value has not yet been propagated to "main" memory or other threads.

On the other hand, geti2() effectively accesses the value of i2 from "main" memory. A volatile variable is not allowed to have a local copy of a variable that is different from the value currently held in "main" memory. Effectively, a variable declared volatile must have it's data synchronized across all threads, so that whenever you access or update the variable in any thread, all other threads immediately see the same value. Generally volatile variables have a higher access and update overhead than "plain" variables. Generally threads are allowed to have their own copy of data is for better efficiency.

Volatile Flow:

  1. Get a global lock on the variable
  2. Update the one variable from main memory
  3. Write any change of the one variable back to main memory
  4. Release the lock

Synchronized Flow:

  1. Get a global lock on the monitor
  2. Update all shared variables that have been accessed from main memory
  3. Process some statements
  4. Write all shared variables that have been changed back to main memory
  5. Release the lock

There are two differences between volitile and synchronized.

Firstly synchronized obtains and releases locks on monitors which can force only one thread at a time to execute a code block. That's the fairly well known aspect to synchronized. But synchronized also synchronizes memory. In fact synchronized synchronizes the whole of thread memory with "main" memory. So executing geti3() does the following:

  1. The thread acquires the lock on the monitor for object this .
  2. The thread memory flushes all its variables, i.e. it has all of its variables effectively read from "main" memory .
  3. The code block is executed (in this case setting the return value to the current value of i3, which may have just been reset from "main" memory).
  4. (Any changes to variables would normally now be written out to "main" memory, but for geti3() we have no changes.)
  5. The thread releases the lock on the monitor for object this.

So where volatile only synchronizes the value of one variable between thread memory and "main" memory, synchronized synchronizes the value of all variables between thread memory and "main" memory, and locks and releases a monitor to boot. Clearly synchronized is likely to have more overhead than volatile.

http://javaexp.blogspot.com/2007/12/difference-between-volatile-and.html

krmby
Very thorough, thanks!
Albus Dumbledore