How atomicity is achieved in the classes defined i

2019-06-16 17:12发布

问题:

I was going through the source code of java.util.concurrent.atomic.AtomicInteger to find out how atomicity is achieved by the atomic operations provided by the class. For instance AtomicInteger.getAndIncrement() method source is as follows

public final int getAndIncrement() {
    for (;;) {
        int current = get();
        int next = current + 1;
        if (compareAndSet(current, next))
            return current;
    }
}

I am not able to understand the purpose of writing the sequence of operations inside a infinite for loop. Does it serve any special purpose in Java Memory Model (JMM). Please help me find a descriptive understanding. Thanks in advance.

回答1:

I am not able to understand the purpose of writing the sequence of operations inside a infinite for loop.

To understand why it is in an infinite loop I find it helpful to understand what the compareAndSet does and how it may return false.

Atomically sets the value to the given updated value if the current
value == the expected value.

 Parameters:
     expect - the expected value
     update - the new value 
 Returns:
     true if successful. False return indicates that the actual value was not
     equal to the expected value

So you read the Returns message and ask how is that possible?

If two threads are invoking incrementAndGet at close to the same time, and they both enter and see the value current == 1. Both threads will create a thread-local next == 2 and try to set via compareAndSet. Only one thread will win as per documented and the thread that loses must try again.

This is how CAS works. You attempt to change the value if you fail, try again, if you succeed then continue on.

Now simply declaring the field as volatile will not work because incrementing is not atomic. So something like this is not safe from the scenario I explained

volatile int count = 0;

public int incrementAndGet(){
   return ++count; //may return the same number more than once.
}


回答2:

I am not able to understand the purpose of writing the sequence of operations inside a infinite for loop.

The purpose of this code is to ensure that the volatile field gets updated appropriately without the overhead of a synchronized lock. Unless there are a large number of threads all competing to update this same field, this will most likely spin a very few times to accomplish this.

The volatile keyword provides visibility and memory synchronization guarantees but does not in itself ensure atomic operations with multiple operations (test and set). If you are testing and then setting a volatile field there are race-conditions if multiple threads are trying to perform the same operation at the same time. In this case, if multiple threads are trying to increment the AtomicInteger at the same time, you might miss one of the increments. The concurrent code here uses the spin loop and the compareAndSet underlying methods to make sure that the volatile int is only updated to 4 (for example) if it still is equal to 3.

  1. t1 gets the atomic-int and it is 0.
  2. t2 gets the atomic-int and it is 0.
  3. t1 adds 1 to it
  4. t1 atomically tests to make sure it is 0, it is, and stores 1.
  5. t2 adds 1 to it
  6. t2 atomically tests to make sure it is 0, it is not, so it has to spin and try again.
  7. t2 gets the atomic-int and it is 1.
  8. t2 adds 1 to it
  9. t2 atomically tests to make sure it is 1, it is, and stores 2.

Does it serve any special purpose in Java Memory Model (JMM).

No, it serves the purpose of the class and method definitions and uses the JMM and the language definitions around volatile to achieve its purpose. The JMM defines what the language does with the synchronized, volatile, and other keywords and how multiple threads interact with cached and central memory. This is mostly about native code interactions with operating system and hardware and is rarely, if ever, about Java code.

It is the compareAndSet(...) method which gets closer to the JMM by calling into the Unsafe class which is mostly native methods with some wrappers:

public final boolean compareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}


回答3:

Java's compareAndSet is based on CPU compare-and-swap (CAS) instructions see http://en.wikipedia.org/wiki/Compare-and-swap. It compares the contents of a memory location to a given value and, only if they are the same, modifies the contents of that memory location to a given new value.

In case of incrementAndGet we read the current value and call compareAndSet(current, current + 1). If it returns false it means that another thread interfered and changed the current value, which means that our attempt failed and we need to repeat the whole cycle until it succeeds.