13

Since Atomic means thread safe. When do we ever use compareAndSet when .set() itself is Atomic and thread safe in java?

say for example I want to set a variable atomically such that every other thread can see it (but I want the variable to be set in a thread safe manner) I could simple declare it as volatile AtomicBoolean or volatile AtomicInteger and that should be good right? what are some of cases where I need to use compareAndSet?

Trying
  • 14,004
  • 9
  • 70
  • 110
user1870400
  • 6,028
  • 13
  • 54
  • 115
  • 2
    How would you do it if you want to set the value to `100` **if and only if the current value is `300`** for example. You cannot read the value and compare it yourself, because during your comparison some other thread could have updated the value. – Codebender Jun 09 '16 at 03:56
  • 3
    Thread-safety is not _composeable_. I.e., Building something entirely out of thread-safe classes will _not_ guarantee that the thing you built is thread-safe. If your program naively performs an atomic compare operation followed by an atomic set operation, that _sequence_ of operations is not atomic. – Solomon Slow Jun 09 '16 at 15:33

5 Answers5

15

There are two important concepts in multithreading environment.

  1. atomicity
  2. visibility

Volatile solves the visibility problem but it does not deal with atomicity e.g. i++. Here i++ is not a single machine instruction rather it is three machine instructions.

  1. copy the value to register
  2. increment it
  3. place it back

AtomicInteger, AtomicReference are based on the Compare and swap instruction. CAS has three operands a memory location V on which to operate, the expected old value A, and the new value B. CAS atomically updates V to the new value B, but only if the value in V matches the expected old value A; otherwise it does nothing. In either case, it returns the value currently in V. This is used by JVM in AtomicInteger, AtomicReference and they call the function as compareAndSet() if this functionality is not supported by underlying processor then JVM implements it by spin lock.

Set is atomic (it is not always correct) but compare and then set is not atomic. So when you have a requirement for this e.g. when the value is X then only change to Y so to do this atomically you need this kind of primitives you can use compareAndSet of AtomicInteger, AtomicReference e.g. atomicLong.compareAndSet(long expect, long update)

You can actually use this primitives to develop powerful datastrucutures like Concurrent stack.

import java.util.concurrent.atomic.AtomicReference;

public class MyConcurrentStack<T> {

    private AtomicReference<Node> head = new AtomicReference<Node>();

    public MyConcurrentStack() {
    }

    public void push(T t) {
        if (t == null) {
            return;
        }
        Node<T> n = new Node<T>(t);
        Node<T> current;

        do {
            current = head.get();
            n.setNext(current);
        } while (!head.compareAndSet(current, n));
    }

    public T pop() {
        Node<T> currentHead = null;
        Node<T> futureHead = null;
        do {
            currentHead = head.get();
            if (currentHead == null) {
                return null;
            }
            futureHead = currentHead.next;
        } while (!head.compareAndSet(currentHead, futureHead));

        return currentHead.data;
    }

    /**
     *
     * @return null if no element present else return a element. it does not
     * remove the element from the stack.
     */
    public T peek() {
        Node<T> n = head.get();
        if (n == null) {
            return null;
        } else {
            return n.data;
        }
    }

    public boolean isEmpty() {
        if (head.get() == null) {
            return true;
        }
        return false;
    }

    private static class Node<T> {

        private final T data;
        private Node<T> next;

        private Node(T data) {
            this.data = data;
        }

        private void setNext(Node next) {
            this.next = next;
        }
    }
}
Trying
  • 14,004
  • 9
  • 70
  • 110
  • yeah from what I read compareAndSet is atomic as well. Also all I am asking is when do you ever use compareAndSet? What are the typical use cases ? – user1870400 Jun 09 '16 at 03:55
  • so compareAndSet is not atomic? – user1870400 Jun 09 '16 at 04:01
  • 1
    yes it is atomic that is what i explained (how it is achieved by the CAS machine instruction ), correct? – Trying Jun 09 '16 at 04:02
  • 2
    There's another use case for CAS that's simpler and probably even more common: incrementing an int. If you look at the code for incrementAndGet, you'll see it internally [uses a CAS loop](http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8u40-b25/sun/misc/Unsafe.java#Unsafe.getAndAddInt%28java.lang.Object%2Clong%2Cint%29). – yshavit Jun 09 '16 at 04:37
  • Apart from visibility and atomicity, another key part is ordering. So how loads/stores are ordered in the global memory order with respect to other loads/stores. – pveentjer Oct 23 '21 at 05:04
4

A simple write operation is inherently atomic (in most cases). There's nothing special about set(). If you look at the source code for AtomicInteger.set(), you'll see this:

public final void set(int newValue) {
    value = newValue;
}

The magic of atomic classes is that they can read and modify atomically. In a multi-threaded environment, if you try to implement compare-and-set logic using a simple if, you might read a value, run some calculation and try to update the variable. But between your read and write operations, the value could have been updated by another thread, invalidating your calculation. Atomic classes make sure that nothing gets between your reads and writes. Hence the compareAndSet() methods, along with getAndSet(), getAndIncrement() etc.

shmosel
  • 49,289
  • 6
  • 73
  • 138
  • is compareAndSet Atomic? – user1870400 Jun 09 '16 at 04:02
  • 3
    @user1870400, it is on the [atomic classes](https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/atomic/package-summary.html). That's the point. – shmosel Jun 09 '16 at 04:03
  • why would set() be atomic if getAndSet() is documented as atomic unlike set() [cf](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicReference.html) – user1767316 Oct 05 '21 at 12:32
  • @user1767316 Atomic means an operation is indivisible. Writing an int is always a single operation; therefore atomicity is irrelevant. – shmosel Oct 05 '21 at 23:47
4

You use compareAndSet when you need to update some data already existent in AtomicReference and then put it back to this reference.

For example: you need to increment a value from several threads (or update it in any other way).

Integer n = new Integer(10);
AtomicReference<Integer> ref = new AtomicReference<>(n);

// later...
int i = ref.get().intValue(); // i == 10

// some other thread increments the value in ref and now it is 11

ref.set(new Integer(i + 1));

// Oops! Now ref contains 11, but should be 12.

But with compareAndSet we could increase it atomically.

Integer n = new Integer(10);
AtomicReference<Integer> ref = new AtomicReference<>(n);

// later...

boolean success = false;
do {
    Integer old = ref.get(); // old == 10 on first pass, 11 on second pass
    // On first pass some other thread incrments the value in ref and now it is 11
    Integer updated = new Integer(old + 1);
    success = ref.compareAndSet(old, updated);
    // On first pass success will be false and value in ref will not update
} while (!success);

// Now ref contains 12 if other thread increments it to 11 between get and set.
Ruslan Stelmachenko
  • 4,987
  • 2
  • 36
  • 51
3

compareAndSet is an essential primitive for non-blocking algorithms.

For example, it's basically impossible to implement wait-free algorithms with atomic read/write only — there must be compareAndSet or something similar.

compareAndSet is much more powerful that atomic read/write.
The power is expressed in consensus number:

  • compareAndSet has consensus number infinity
  • atomic read/write has consensus number 1

Consensus number is the maximum number of threads for which the concurrent object can solve a consensus problem (threads propose their candidate values and agree on a single consensus value) in a wait-free (i.e. without blocking, every thread is guaranteed to complete in no more that some fixed max number of steps) implementation.
It was proven that objects with consensus number of n can implement any object with a consensus number of n or lower, but cannot implement any objects with higher consensus number.

More explanations can be found in "The Art of Multiprocessor Programming" by M. Herlihy and N. Shavit.

0

Set is atomic and used to set a new value. compareAndSet compares old value and if it equals to current value, sets new value. If we use set instead of compareAndSet:

if(atomic.get().equals(12)) {
    atomic.set(13);
}

This will not be thread-safe, because it causes race condition. Result of execution depends on the timing and order of threads. For example, when thread1 gets the value, thread2 can change it. Compound operations such as check-and-act, read-modify-write must be executed as atomic.