Differencebetween Atomic/Volatile/Synchronized

What is the difference between atomic / volatile / synchronized?

You are specifically asking about how they internally work, so here you are:

No synchronization

private int counter;

public int getNextUniqueIndex() {
return counter++;
}

It basically reads value from memory, increments it and puts back to memory. This works in single thread but nowadays, in the era of multi-core, multi-CPU, multi-level caches it won't work correctly. First of all it introduces race condition (several threads can read the value at the same time), but also visibility problems. The value might only be stored in "local" CPU memory (some cache) and not be visible for other CPUs/cores (and thus - threads). This is why many refer to local copy of a variable in a thread. It is very unsafe. Consider this popular but broken thread-stopping code:

private boolean stopped;

public void run() {
while(!stopped) {
//do some work
}
}

public void pleaseStop() {
stopped = true;
}

Add volatile to stopped variable and it works fine - if any other thread modifies stopped variable via pleaseStop() method, you are guaranteed to see that change immediately in working thread's while(!stopped) loop. BTW this is not a good way to interrupt a thread either, see: How to stop a thread that is running forever without any use and Stopping a specific java thread.

AtomicInteger

private AtomicInteger counter = new AtomicInteger();

public int getNextUniqueIndex() {
return counter.getAndIncrement();
}

The AtomicInteger class uses CAS (compare-and-swap) low-level CPU operations (no synchronization needed!) They allow you to modify a particular variable only if the present value is equal to something else (and is returned successfully). So when you execute getAndIncrement() it actually runs in a loop (simplified real implementation):

int current;
do {
current = get();
} while(!compareAndSet(current, current + 1));

So basically: read; try to store incremented value; if not successful (the value is no longer equal to current), read and try again. The compareAndSet() is implemented in native code (assembly).

volatile without synchronization

private volatile int counter;

public int getNextUniqueIndex() {
return counter++;
}

This code is not correct. It fixes the visibility issue (volatile makes sure other threads can see change made to counter) but still has a race condition. This has been explained multiple times: pre/post-incrementation is not atomic.

The only side effect of volatile is "flushing" caches so that all other parties see the freshest version of the data. This is too strict in most situations; that is why volatile is not default.

volatile without synchronization (2)

volatile int i = 0;
void incIBy5() {
i += 5;
}

The same problem as above, but even worse because i is not private. The race condition is still present. Why is it a problem? If, say, two threads run this code simultaneously, the output might be + 5 or + 10. However, you are guaranteed to see the change.

Multiple independent synchronized

void incIBy5() {
int temp;
synchronized(i) { temp = i }
synchronized(i) { i = temp + 5 }
}

Surprise, this code is incorrect as well. In fact, it is completely wrong. First of all you are synchronizing on i, which is about to be changed (moreover, i is a primitive, so I guess you are synchronizing on a temporary Integer created via autoboxing...) Completely flawed. You could also write:

synchronized(new Object()) {
//thread-safe, SRSLy?
}

No two threads can enter the same synchronized block with the same lock. In this case (and similarly in your code) the lock object changes upon every execution, so synchronized effectively has no effect.

Even if you have used a final variable (or this) for synchronization, the code is still incorrect. Two threads can first read i to temp synchronously (having the same value locally in temp), then the first assigns a new value to i (say, from 1 to 6) and the other one does the same thing (from 1 to 6).

The synchronization must span from reading to assigning a value. Your first synchronization has no effect (reading an int is atomic) and the second as well. In my opinion, these are the correct forms:

void synchronized incIBy5() {
i += 5
}

void incIBy5() {
synchronized(this) {
i += 5
}
}

void incIBy5() {
synchronized(this) {
int temp = i;
i = temp + 5;
}
}

Volatile Vs Atomic

The effect of the volatile keyword is approximately that each individual read or write operation on that variable is made atomically visible to all threads.

Notably, however, an operation that requires more than one read/write -- such as i++, which is equivalent to i = i + 1, which does one read and one write -- is not atomic, since another thread may write to i between the read and the write.

The Atomic classes, like AtomicInteger and AtomicReference, provide a wider variety of operations atomically, specifically including increment for AtomicInteger.

Difference between volatile and synchronized in Java

It's important to understand that there are two aspects to thread safety.

  1. execution control, and
  2. memory visibility

The first has to do with controlling when code executes (including the order in which instructions are executed) and whether it can execute concurrently, 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 private copies of main memory.

Using synchronized prevents any other thread from obtaining the monitor (or lock) for the same object, thereby preventing all code blocks protected by synchronization on the same object from executing concurrently. Synchronization also creates a "happens-before" memory barrier, causing a memory visibility constraint such that anything done up to the point some thread releases a lock appears to another thread subsequently acquiring the same lock to have happened before it acquired the lock. In practical terms, on current hardware, this typically causes flushing of the CPU caches when a monitor is acquired and writes to main memory when it is released, both of which are (relatively) expensive.

Using volatile, on the other hand, 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 useful for some actions where it is simply required that visibility of the variable be correct and order of accesses is not important. Using volatile also changes treatment of long and double to require accesses to them to be atomic; on some (older) hardware this might require locks, though not on modern 64 bit hardware. Under the new (JSR-133) memory model for Java 5+, the semantics of volatile have been strengthened to be almost as strong as synchronized with respect to memory visibility and instruction ordering (see http://www.cs.umd.edu/users/pugh/java/memoryModel/jsr-133-faq.html#volatile). For the purposes of visibility, each access to a volatile field acts like half a synchronization.

Under the new memory model, it is still true that volatile variables cannot be reordered with each other. The difference is that it is now no longer so easy to reorder normal field accesses around them. Writing to a volatile field has the same memory effect as a monitor release, and reading from a volatile field has the same memory effect as a monitor acquire. In effect, because the new memory model places stricter constraints on reordering of volatile field accesses with other field accesses, volatile or not, anything that was visible to thread A when it writes to volatile field f becomes visible to thread B when it reads f.

-- JSR 133 (Java Memory Model) FAQ

So, now both forms of memory barrier (under the current JMM) cause an instruction re-ordering barrier which prevents the compiler or run-time from re-ordering instructions across the barrier. In the old JMM, volatile did not prevent re-ordering. This can be important, because apart from memory barriers the only limitation imposed is that, for any particular thread, the net effect of the code is the same as it would be if the instructions were executed in precisely the order in which they appear in the source.

One use of volatile is for a shared but immutable object is recreated on the fly, with many other threads taking a reference to the object at a particular point in their execution cycle. One needs the other threads to begin using the recreated object once it is published, but does not need the additional overhead of full synchronization and it's attendant contention and cache flushing.

// Declaration
public class SharedLocation {
static public SomeObject someObject=new SomeObject(); // default object
}

// Publishing code
// Note: do not simply use SharedLocation.someObject.xxx(), since although
// someObject will be internally consistent for xxx(), a subsequent
// call to yyy() might be inconsistent with xxx() if the object was
// replaced in between calls.
SharedLocation.someObject=new SomeObject(...); // new object is published

// Using code
private String getError() {
SomeObject myCopy=SharedLocation.someObject; // gets current copy
...
int cod=myCopy.getErrorCode();
String txt=myCopy.getErrorText();
return (cod+" - "+txt);
}
// And so on, with myCopy always in a consistent state within and across calls
// Eventually we will return to the code that gets the current SomeObject.

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 thread-1 does the test for counter==1000 and finds it true and is then suspended. Then thread-2 does the same test and also sees it true and is suspended. Then thread-1 resumes and sets counter to 0. Then thread-2 resumes and again sets counter to 0 because it missed the update from thread-1. 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 only written back to main memory at some indeterminate point later when a memory barrier occurred or when the cache memory was needed for something else. Making the counter volatile is insufficient for thread-safety of this code, because the test for the maximum and the assignments are discrete operations, including the increment which is a set of non-atomic read+increment+write machine instructions, something like:

MOV EAX,counter
INC EAX
MOV counter,EAX

Volatile variables are useful only when all operations performed on them are "atomic", such as my example where a reference to a fully formed object is only read or written (and, indeed, typically it's only written from a single point). Another example would be a volatile array reference backing a copy-on-write list, provided the array was only read by first taking a local copy of the reference to it.

Understanding atomic-ness, visibility and reordering of synchonized blocks vs volatile variables in Java

The key difference between 'synchronized' and 'volatile', is that 'synchronized' can make threads pause, whereas volatile can't.

'caches and registers' isn't a thing. The book says that because in practice that's usually how things are implemented, and it makes it easier (or perhaps not, given these questions) to understand the how & why of the JMM (java memory model).

The JMM doesn't name them, however. All it says is that the VM is free to give each thread its own local copy of any variable, or not, to be synchronized at some arbitrary time with some or all other threads, or not... unless there is a happens-before relationship anywhere, in which case the VM must ensure that at the point of execution between the two threads where a happens before relationship has been established, they observe all variables in the same state.

In practice that would presumably mean to flush caches. Or not; it might mean the other thread overwrites its local copy.

The VM is free to implement this stuff however it wants, and differently on every architecture out there. As long as the VM sticks to the guaranteed that the JMM makes, it's a good implementation, and as a consequence, your software must work given solely those guarantees and no other assumptions; because what works on your machine might not work on another if you rely on assumptions that aren't guaranteed by the JMM.

Reordering

Reordering is also not in the VM spec, at all. What IS in the VM spec are the following two concepts:

  1. Within the confines of a single thread, all you can observe from inside it is consistent with an ordered view. That is, if you write 'x = 5; y = 10;' it is not possible to observe, from within the same thread, y being 10 but x being its old value. Regardless of synchronized or volatile. So, anytime it can reorder things without that being observable, then the VM is free to. Will it? Up to the VM. Some do, some don't.

  2. When observing effects caused by other threads, and you have not established a happens-before relationship, you may see some, all, or none of these effects, in any order. Really, anything can happen here. In practice, then: Do NOT attempt to observe effects caused by other threads without establishing a happens-before, because the results are arbitrary and untestable.

Happens-before relationships are established by all sorts of things; synchronized blocks obviously do it (if your thread is frozen trying to acquire a lock, and it then runs, any synchronized blocks on that object that finished 'happened before', and anything they did you can now observe, with the guarantee that what you observe is consistent with those things having run in order, and where all data they wrote you can see (as in, you won't get an older 'cache' or whatnot). Volatile accesses do as well.

Atomicity

Yes, your interpretation of why x++ is not atomic even if x is volatile, is correct.

I'm not sure what your Q4 is trying to ask.

In general, if you want to atomically increment an integer, or do any of many other concurrent-esque operations, look at the java.util.concurrent package. These contain efficient and useful implementations of various concepts. AtomicInteger, for example, can be used to atomically increment something, in a way that is visible from other threads, while still being quite efficient (for example, if your CPU supports Compare-And-Set (CAS) operations, Atomicinteger will use it; not something you can do from general java without resorting to Unsafe).

Difference between Volatile and synchronized

With synchronized you included the increment and set operations in the same critical section. With volatile the increment and the set were separately protected. That means one thread could increment, then the other, then one could set followed by the other, not necessarily in the same order they incremented. volatile can only coordinate single actions; synchronized can coordinate sequences of actions​.

Java Volatile ,synchronization,atomic example

The effect of the volatile keyword is approximately that each individual read or write operation on that variable is atomic.

Notably, however, an operation that requires more than one read/write -- such as i++, which is equivalent to i = i + 1, which does one read and one write -- is not atomic, since another thread may write to i between the read and the write.

The Atomic classes, like AtomicInteger and AtomicReference, provide a wider variety of operations atomically, specifically including increment for AtomicInteger.

That's why you need to care about atomicity in statements like counter = counter + 1

Please check this post Volatile Vs Atomic



Related Topics



Leave a reply



Submit