CAS(Compare And Swap)

在Java中,談到線程安全,首先想到的就是synchronized關鍵字。但是該同步方法性能低下是不爭的事實,在高并發(fā)的應用中存在性能問題。于是,人們想出了CAS,即Compare And Swap的縮寫。仰仗這一思路,實現(xiàn)了JUC中很多并發(fā)工具,搞清楚它的來龍去脈很有必要。本文初步介紹CAS以及其使用方法。

CAS的概念

從其字面意思來看,就是比較再交換。那么構成CAS機制的關鍵元素是什么?
CAS機制中使用了3個基本操作數(shù):

  • 內存地址V
  • 內存中存放的舊的預期值A
  • 要修改的新值B

更新一個變量的時候,只有當變量的預期值A和內存地址V當中的實際值相同時,才會將內存地址V對應的值修改為B。與synchronized的悲觀鎖思路不同,這種思路是典型的樂觀鎖。該機制保證了并發(fā)安全,但是不能保證并發(fā)同步。

CAS的優(yōu)缺點

優(yōu)點

1.無鎖,永遠不會死鎖,是一種輕量級的樂觀鎖。

缺點

1.會有ABA的問題,可以使用AtomicStampedReference解決,本質是增加一個版本信息(timestamp)
2.嘗試更新的自旋操作會不斷地消耗CPU資源

實現(xiàn)機制

Java中,CAS依賴的是Unsafe提供的一系列原子操作。其中提供了很多的原子方法。其中用的比較多的方法如下:

  • compareAndSet(oldValue, newValue)
    通常是返回boolean,嘗試設置一次,失敗返回false;成功返回true。如果newValue依賴oldValue,則需要調用該方法的地方實現(xiàn)自旋(不停的循環(huán)調用,直到成功),參見下節(jié)中的源碼。

  • getAndSet(newValue)
    內部執(zhí)行自旋操作。即:循環(huán)查找最新的值,然后嘗試調用compareAndSet來設置newValue。下面是AtomicBoolean的方法代碼,一看便知。

public final boolean getAndSet(boolean newValue) {
    boolean prev;
    do {
        prev = get();// 這里的值很可能在調用compareAndSet前被修改了。
    } while (!compareAndSet(prev, newValue));
    return prev;
}

CAS的使用場景

最常用的場景就是從V中讀取A,并根據(jù)A做計算得到B,然后通過CAS以原子的方式把V中的A變成B。下面是代碼示例。

public class AtomicReferenceMain {
    static final AtomicReference<User> AI = new AtomicReference<User>(new User());

    public static class WorkThread implements Runnable {
        AtomicReference<User> ai;

        public WorkThread(AtomicReference<User> ai) {
            this.ai = ai;
        }

        public void run() {
            int i = 0;
            while (i < 10) {
                // 標志著CAS操作是否成功。
                boolean res = false;
                User newValue = null;
                while (!res) {
                    // 自旋。每次更新前都獲取最新值,直到更新成功。
                    User a = AI.get();
                    // 創(chuàng)建新對象
                    newValue = new User();
                    newValue.setAge(a.getAge() + 1);// 在上一次值的基礎上+1
                    newValue.setName("name" + i);
                    // 嘗試設置新值,如果失敗,則重新嘗試。此操作成為自旋。
                    // 如果執(zhí)行此方法的時候,有其他線程修改了引用,則compareAndSet更新失敗,此時重試。
                    // 這里需要注意的是,如果更新失敗,始終要從AI中獲取最新的值,在這個基礎上再次嘗試更新。
                    // 這樣才能保證線程安全性。否則運行很多次,最終的age最大數(shù)是變化的。
                    res = AI.compareAndSet(a, newValue);
                    
                    // 如果這里的新對象,跟之前的對象值(比如age)無關,
                    // 那么可以簡單的調用AI.getAndSet()方法,
                    // AI.getAndSet(newValue)會自動自旋。獲取AI當前的最新值,然后把這個值作為老值,
                    // 用來更新newValue。直到更新成功為止。
                }
                System.out.println(Thread.currentThread().getName() + ":User=" + newValue.toString());

                i++;
            }
        }
    }

    public static void main(String[] args) {
        List<Thread> as = new ArrayList<Thread>(2);
        for (int i = 0; i < 2; i++) {
            as.add(new Thread(new AtomicReferenceMain.WorkThread(AI)));
            as.get(i).start();
        }
    }
}

為了避免過度的自旋,多線程的競爭度越低越好。使用的時候需要注意這一點。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容