当set在Java中已经是原子时,为什么我们需要compareAndSet?

时间:2016-06-09 03:40:12

标签: java multithreading concurrency compare-and-swap

因为Atomic意味着线程安全。当.set()本身在Java中是Atomic和线程安全时,我们什么时候使用compareAndSet?

比如说我想原子地设置一个变量,这样每个其他线程都可以看到它(但我希望变量以线程安全的方式设置)我可以简单地将它声明为易失性AtomicBoolean或volatile AtomicInteger好吗?在什么情况下我需要使用compareAndSet?

4 个答案:

答案 0 :(得分:5)

多线程环境中有两个重要概念。

  1. 原子
  2. 能见度
  3. Volatile解决了可见性问题,但它不涉及原子性,例如我++。在这里,i ++不是一个机器指令,而是三个机器指令。

    1. 将值复制到注册
    2. 增加它
    3. 放回去
    4. AtomicIntegerAtomicReference基于Compare和swap指令。 CAS有三个操作数,一个操作的存储位置V,预期的旧值A和新值B.CAS原子地将V更新为新值B,但仅当V中的值与预期的旧值A匹配时;否则它什么都不做。在任何一种情况下,它都返回当前V中的值。这由JVM在AtomicIntegerAtomicReference中使用,如果底层处理器不支持此功能,则它们将函数调用为compareAndSet() JVM通过自旋锁实现它。

      Set是原子的(它并不总是正确的)但是比较然后设置不是原子的。所以当你需要这个 时。当值为X然后只改为Y以便原子地执行此操作时需要这种原语 您可以使用AtomicInteger的compareAndSet,AtomicReference例如atomicLong.compareAndSet(long expect, long update)

      您实际上可以使用此原语来开发强大的数据结构,如并发堆栈。

      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;
              }
          }
      }
      

答案 1 :(得分:4)

简单的写操作本质上是原子的(在大多数情况下)。 AtomicInteger.set()没有什么特别之处。如果您查看public final void set(int newValue) { value = newValue; } 的{​​{3}},您会看到:

if

原子类的神奇之处在于它们可以原子地读取和修改。在多线程环境中,如果您尝试使用简单的compareAndSet()实现比较和设置逻辑,您可能会读取一个值,运行一些计算并尝试更新变量。但是在读取和写入操作之间,该值可能已被另一个线程更新,从而使计算无效。原子类确保读写之间没有任何内容。因此,getAndSet()方法以及getAndIncrement()android:windowSoftInputMode="adjustPan" <activity ... ... android:windowSoftInputMode="adjustPan"> ... </activity>

答案 2 :(得分:0)

Set是原子的,用于设置新值。 compareAndSet比较旧值,如果它等于当前值,则设置新值。如果我们使用set代替compareAndSet

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

这不是线程安全的,因为它会导致竞争条件。执行结果取决于线程的时间和顺序。例如,当thread1获取值时,thread2可以更改它。复合操作(如check-and-act,read-modify-write)必须以原子方式执行。

答案 3 :(得分:0)

当您需要更新 AtomicReference中已存在的某些数据时,使用compareAndSet,然后将其重新添加到此引用中。

例如:您需要从多个线程中增加一个值(或以任何其他方式更新它)。

Number n = new Integer(10);
AtomicReference ref = new AtimicReference(n);

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

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

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

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

但是compareAndSet我们可以原子地增加它。

Number n = new Integer(10);
AtomicReference ref = new AtimicReference(n);

// later...

boolean success = false;
do {
    Integer old = 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.