繁体   English   中英

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

[英]Why do we need to compareAndSet when set is already atomic in Java?

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

例如,我想以原子方式设置一个变量,以便其他每个线程都可以看到它(但我希望以线程安全的方式设置变量)我可以简单地将其声明为 volatile AtomicBoolean 或 volatile AtomicInteger,这应该是对的? 在哪些情况下我需要使用 compareAndSet?

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

  1. 原子
  2. 能见度

Volatile解决了可见性问题,但它不涉及原子性,例如i ++。 在这里,i ++不是一个单一的机器指令,而是三个机器指令。

  1. 将值复制到注册
  2. 增加它
  3. 把它放回去

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

简单的写操作本质上是原子的(在大多数情况下)。 set()没有什么特别之处。 如果你看一下AtomicInteger.set()源代码 ,你会看到:

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

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

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

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

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

当您需要更新 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.

compareAndSet非阻塞算法的基本原语。

例如,基本上不可能用atomic read/write来实现免等待算法——必须有compareAndSet或类似的东西。

compareAndSetatomic read/write compareAndSet大得多。
权力以共识数表示:

  • compareAndSet有共识数无穷大
  • atomic read/write具有共识编号 1

共识数是并发对象可以在无等待(即无阻塞的情况下,保证每个线程在没有问题的情况下完成)解决共识问题(线程提出其候选值并就单个共识值达成一致)的最大线程数更多的是一些固定的最大步骤数)实现。
已经证明,共识数为n对象可以实现任何共识数为n或更低的对象,但不能实现任何具有更高共识数的对象。

更多解释可以在 M. Herlihy 和 N. Shavit 的“多处理器编程艺术”中找到。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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