2

Hi I am reading java concurrency in practice and I read interesting statement states that

Locking can guarantee both visibility and atomicity; volatile variables can only guarantee visibility.

Can any one please explain that if declaring a variable as volatile all other reading threads getting updated values so why do i care about the atomicity in statement like: counter = counter + 1;

Thanks in advance.

Michael
  • 41,989
  • 11
  • 82
  • 128
so_what
  • 176
  • 10
  • check out this post. https://stackoverflow.com/questions/3519664/difference-between-volatile-and-synchronized-in-java – Türkmen Mustafa Demirci Dec 18 '17 at 10:06
  • 1
    "Why do i care about the atomicity in statement like `counter = counter + 1;`". Well if you want your counter to have correct values, you should care. – Kayaman Dec 18 '17 at 10:14
  • @so_what : Please mark the answer right if it helps you – Aarish Ramesh Dec 18 '17 at 10:19
  • One last doubt which troubles me that should i think atomic variables=volatile(for get recent write value)+synchronized(to ensure atomicity) ? – so_what Dec 18 '17 at 10:26
  • If we didn't have the atomic classes (which was the case before jdk 1.5) we would have to use volatile+synchronized. But synchronization has a performance overhead. The atomic classes are more efficient for this purpose. See https://docs.oracle.com/javase/tutorial/essential/concurrency/atomicvars.html – DodgyCodeException Dec 18 '17 at 11:54

2 Answers2

3

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

Aarish Ramesh
  • 6,745
  • 15
  • 60
  • 105
  • This answer seems to state how to fix the problems caused by these non-atomic operations. But it doesn't answer the question of why it's a problem to begin with. Namely in the case of a variable being incremented in two threads can result in only a single increment if both threads read the same value before incrementing – Cruncher Dec 18 '17 at 10:28
  • "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" Does it not answer the question of why does atomicity needs to be cared ? Please read it clearly before concluding anything @Cruncher – Aarish Ramesh Dec 18 '17 at 10:29
  • "another thread may write to i between the read and the write" statement makes sense for me because I have done demo on this but can you consider the following use cases for thread-safe: 1)Volatile +atomic variables(not individauly)=perfect 2)volatile+synchronized block=perfect 3)individual atmoic operation+synchronized=perfect – so_what Dec 18 '17 at 10:33
  • @Aarish That's actually just restated the definition of being non-atomic. If I was new to thread safety I'd have many questions about what that actually means. **why** is it a problem if threads read between the read and write? – Cruncher Dec 18 '17 at 16:54
  • @Cruncher It is a problem if threads write between read and write and not with reads – Aarish Ramesh Dec 19 '17 at 06:28
2

Here's a self-contained example executable application that demonstrates that volatile on its own is not enough. Four threads increment a counter 10,000 times each, so you'd expect the counter to be 40,000 at the end. It uses a primitive int variable and an AtomicInt, and tries the exercise 5 times each.

import java.util.Collections;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

class AtomicDemo {
    interface Demo extends Callable<Void> {
        int getCounter();
    }

    static class UsePrimitive implements Demo {
        private volatile int counter = 0;

        public Void call() throws Exception {
            for (int i = 1; i <= 10000; ++i) {
                ++counter;
            }
            return null;
        }

        public int getCounter() {
            return counter;
        }
    }

    static class UseAtomic implements Demo {
        final AtomicInteger counter = new AtomicInteger(0);

        public Void call() throws Exception {
            for (int i = 1; i <= 10000; ++i) {
                counter.incrementAndGet();
                System.out.print("");
            }
            return null;
        }

        public int getCounter() {
            return counter.get();
        }
    }

    public static void main(String[] args) throws Exception {
        ExecutorService exec = Executors.newFixedThreadPool(4);
        for (int i = 1; i <= 5; ++i) {
            Demo demo = new UsePrimitive();
            exec.invokeAll(Collections.nCopies(4, demo));
            System.out.println("Count to 40000 using primitive, attempt number " + i + ": " + demo.getCounter());
        }
        for (int i = 1; i <= 5; ++i) {
            Demo demo = new UseAtomic();
            exec.invokeAll(Collections.nCopies(4, demo));
            System.out.println("Count to 40000 using atomic, attempt number " + i + ": " + demo.getCounter());
        }
        exec.shutdownNow();
    }
}

Typical output:

Count to 40000 using primitive, attempt number 1: 39711
Count to 40000 using primitive, attempt number 2: 39686
Count to 40000 using primitive, attempt number 3: 39972
Count to 40000 using primitive, attempt number 4: 39840
Count to 40000 using primitive, attempt number 5: 39865
Count to 40000 using atomic, attempt number 1: 40000
Count to 40000 using atomic, attempt number 2: 40000
Count to 40000 using atomic, attempt number 3: 40000
Count to 40000 using atomic, attempt number 4: 40000
Count to 40000 using atomic, attempt number 5: 40000

You see, only with AtomicInt do you always get the expected results.

DodgyCodeException
  • 5,963
  • 3
  • 21
  • 42
  • great I tried also and come to conclusion that volatile is only for getting the latest value for the thread not prevent violation of atomicity ,if context switching happen at some unfortunate time then we will not get the correct value.Also when using Atomicxxx classes they are using volatile variable for get the laatest value and then uses synchronization to update the value correctly. – so_what Dec 18 '17 at 16:28