1. 程式人生 > >Java CAS 和 synchronized 和 Lock

Java CAS 和 synchronized 和 Lock

CAS 機制

  1. 適用場景:樂觀認為併發不高,不需要阻塞,可以不上鎖。
  2. 特點:不斷比較更新,直到成功。
  3. 缺點:高併發cpu壓力大;ABA問題。

ABA問題:
CAS機制生效的前提是,取出記憶體中某時刻的資料,而在下時刻比較並替換。
如果在比較之前,資料發生了變化,例如:A->B->A,即A變為B然後又變化A,那麼這個資料還是發生了變化,但是CAS還是會成功。

Java中CAS機制使用版本號進行對比,避免ABA問題。

synchronized

  1. 適用場景:悲觀認為併發很高,需要阻塞,需要上鎖。
  2. 特點:語言層面的優化,鎖粗化、偏向鎖、輕量鎖等等;可讀性高。

ReentrantLock 和 Atomic類

以上兩種併發工具都使用了CAS機制。
在併發不高競爭不激烈時候,效能略低於synchronized;相反,併發高競爭激烈時候,效能高於synchronized。

ReentrantLock相對於synchronized:

  • ReentrantLock等待可中斷,synchronized不可以。
  • ReentrantLock需要手動釋放鎖,synchronized不需要。
  • ReentrantLock可支援公平非公平鎖,synchronized只支援非公平鎖。
  • ReentrantLock沒有語言層面的優化,底層實現機制AQS和CAS,synchronized有優化。
  • ReentrantLock可重入鎖,synchronized不可重入,可能導致死鎖。
  • ReentrantLock支援讀寫鎖,可以提高高併發讀操作。
  • synchronized由作業系統支援,涉及核心態和使用者態的上下文切換,併發高時切換開銷非常大。
  • ReentrantLock(AQS)依賴volatile int變數標示鎖狀態,結構為雙向連結串列的等待佇列,通過(cas+死迴圈)更改鎖狀態,一旦更新成功,標示競爭到鎖。

AQS釋放鎖:
通過cas改變狀態

    private void doReleaseShared() {
        /*
         * Ensure that a release propagates, even if there are other
         * in
-progress acquires/releases. This proceeds in the usual * way of trying to unparkSuccessor of head if it needs * signal. But if it does not, status is set to PROPAGATE to * ensure that upon release, propagation continues. * Additionally, we must loop in case a new node is added * while we are doing this. Also, unlike other uses of * unparkSuccessor, we need to know if CAS to reset status * fails, if so rechecking. */ for (;;) { Node h = head; if (h != null && h != tail) { int ws = h.waitStatus; if (ws == Node.SIGNAL) { if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) continue; // loop to recheck cases unparkSuccessor(h); } else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) continue; // loop on failed CAS } if (h == head) // loop if head changed break; } }

獲取鎖:
如果獲取到鎖,設為頭節點,否則一直自旋等待,在高併發時,可以避免大量鎖競爭的上下文切換,降低執行緒切換開銷。

private void doAcquireInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }