簡體   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