简体   繁体   中英

Volatile and atomic operation in java

I have read article concerning atomic operation in Java but still have some doubts needing to be clarified:

int volatile num;
public void doSomething() {
  num = 10;  // write operation
  System.out.println(num)   // read
  num = 20;  // write
  System.out.println(num);  // read
}

So i have done wrwr 4 operations on 1 method, are they atomic operations? What will happen if multiple threads invoke doSomething() method simultaneously ?

An operation is atomic if no thread will see an intermediary state, ie the operation will either have completed fully, or not at all.

Reading an int field is an atomic operation, ie all 32 bits are read at once. Writing an int field is also atomic, the field will either have been written fully, or not at all.

However, the method doSomething() is not atomic; a thread may yield the CPU to another thread while the method is being executing, and that thread may see that some, but not all, operations have been executed.

That is, if threads T1 and T2 both execute doSomething(), the following may happen:

T1: num = 10;
T2: num = 10;
T1: System.out.println(num); // prints 10
T1: num = 20;
T1: System.out.println(num); // prints 20
T2: System.out.println(num); // prints 20
T2: num = 20;
T2: System.out.println(num); // prints 20

If doSomething() were synchronized, its atomicity would be guaranteed, and the above scenario impossible.

volatile ensures that if you have a thread A and a thread B, that any change to that variable will be seen by both. So if it at some point thread A changes this value, thread B could in the future look at it.

Atomic operations ensure that the execution of the said operation happens "in one step." This is somewhat confusion because looking at the code 'x = 10;' may appear to be "one step", but actually requires several steps on the CPU. An atomic operation can be formed in a variety of ways, one of which is by locking using synchronized :

  • What the volatile keyword promises.
  • The lock of an object (or the Class in the case of static methods) is acquired, and no two objects can access it at the same time.

As you asked in a comment earlier, even if you had three separate atomic steps that thread A was executing at some point, there's a chance that thread B could begin executing in the middle of those three steps. To ensure the thread safety of the object, all three steps would have to be grouped together to act like a single step. This is part of the reason locks are used.

A very important thing to note is that if you want to ensure that your object can never be accessed by two threads at the same time, all of your methods must be synchronized. You could create a non-synchronized method on the object that would access the values stored in the object, but that would compromise the thread safety of the class.

You may be interested in the java.util.concurrent.atomic library. I'm also no expert on these matters, so I would suggest a book that was recommended to me: Java Concurrency in Practice

Each individual read and write to a volatile variable is atomic. This means that a thread won't see the value of num changing while it's reading it, but it can still change in between each statement. So a thread running doSomething while other threads are doing the same, will print a 10 or 20 followed by another 10 or 20. After all threads have finished calling doSomething , the value of num will be 20.

My answer modified according to Brian Roach's comment.

It's atomic because it is integer in this case.

Volatile can only ganrentee visibility among threads, but not atomic. volatile can make you see the change of the integer, but cannot ganrentee the integration in changes.

For example, long and double can cause unexpected intermediate state.

Atomic Operations and Synchronization:

Atomic executions are performed in a single unit of task without getting affected from other executions. Atomic operations are required in multi-threaded environment to avoid data irregularity.

If we are reading/writing an int value then it is an atomic operation. But generally if it is inside a method then if the method is not synchronized many threads can access it which can lead to inconsistent values. However, int++ is not an atomic operation. So by the time one threads read it's value and increment it by one, other thread has read the older value leading to wrong result.

To solve data inconsistency, we will have to make sure that increment operation on count is atomic, we can do that using Synchronization but Java 5 java.util.concurrent.atomic provides wrapper classes for int and long that can be used to achieve this atomically without usage of Synchronization.

Using int might create data data inconsistencies as shown below:

public class AtomicClass {

    public static void main(String[] args) throws InterruptedException {

        ThreardProcesing pt = new ThreardProcesing();
        Thread thread_1 = new Thread(pt, "thread_1");
        thread_1.start();
        Thread thread_2 = new Thread(pt, "thread_2");
        thread_2.start();
        thread_1.join();
        thread_2.join();
        System.out.println("Processing count=" + pt.getCount());
    }

}

class ThreardProcesing implements Runnable {
    private int count;

    @Override
    public void run() {
        for (int i = 1; i < 5; i++) {
            processSomething(i);
            count++;
        }
    }

    public int getCount() {
        return this.count;
    }

    private void processSomething(int i) {
        // processing some job
        try {
            Thread.sleep(i * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

OUTPUT: count value varies between 5,6,7,8

We can resolve this using java.util.concurrent.atomic that will always output count value as 8 because AtomicInteger method incrementAndGet() atomically increments the current value by one. shown below:

public class AtomicClass {

    public static void main(String[] args) throws InterruptedException {

        ThreardProcesing pt = new ThreardProcesing();
        Thread thread_1 = new Thread(pt, "thread_1");
        thread_1.start();
        Thread thread_2 = new Thread(pt, "thread_2");
        thread_2.start();
        thread_1.join();
        thread_2.join();
        System.out.println("Processing count=" + pt.getCount());
    }
}

class ThreardProcesing implements Runnable {
    private AtomicInteger count = new AtomicInteger();

    @Override
    public void run() {
        for (int i = 1; i < 5; i++) {
            processSomething(i);
            count.incrementAndGet();
        }
    }

    public int getCount() {
        return this.count.get();
    }

    private void processSomething(int i) {
        // processing some job
        try {
            Thread.sleep(i * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

Source: Atomic Operations in java

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM