CAS概念和解析

一、CAS概念  

 

package syncbasics;

import java.util.concurrent.CountDownLatch;

/**
 * 多線程訪問同一份數據,會產生競爭,race condition => 競爭條件
 * 就有可能產生數據的不一致,併發訪問之下產生的不期望出現的結果
 * 如何保障數據的一致呢?---->線程同步(線程執行的順序安排好),
 * 具體:保障操作的原子性(Atomicity)
 * 1.悲觀的認爲這個操作會被別的線程打斷(悲觀鎖)synchronized
 * 2.樂觀的認爲這個操作不會被別的線程打斷(樂觀鎖)cas操作
 * CAS = Compare And Set/Swap/Exchange
 *
 * ++操作:
 * 把n從內存裏面讀到寄存器裏,加完了之後,再寫回去。
 * 還沒來得及寫回去的時候,另外的線程讀到了原值,
 * 因此,++這個正在執行的操作被另外線程打斷了。
 *
 * 只要我們能保證這個執行的操作不被打斷,即保證我這個線程讀過來之後改完這個值1,再寫回去之後,其他線程才能執行,
 * 那最後的結果一定是對的。這種不能夠被打斷的操作稱之爲原子操作。
 *
 * 什麼樣的語句是原子性的?什麼樣的不是?
 * java內存中的8大原子操作,瞭解即可。要查彙編手冊。
 *
 * synchronized:
 * 讓原來的併發變成了序列化。
 * synchronized本身是保證可見性的,n++結束了之後這個線程一定是要和主內存做同步,主內存裏一定都是最新的。
 * synchronized保障了可見性、原子性。
 * 那麼保證有序性嗎?
 * ----不可以,synchronized裏面的代碼塊裏面的操作指令完全有可能換順序。DCL要加volatile就是證明了。
 *
 * CAS概念:
 * 還以下面這個小程序n++操作來說明CAS的概念。
 * n開始等於0,線程1把n讀過來加1,原來是需要加鎖的,現在整個過程不上鎖了,把0讀過來改成1之後,再往回寫
 * 的過程之中做個判斷,判斷原值依然是否爲0,如果依然爲0,說明在線程1讀0加1的過程中,沒有人來過,那就直接把1
 * 寫回去,搞定。
 * 萬一中間有人改了呢?萬一其他線程已經將0改成8了,線程1把1往回寫的時候發現原值已經變爲8了,不是你所期望的0,
 * 這時候怎麼辦?那就再來一遍,把8讀出來加1變成9,把9往回寫的過程之中看看判斷原值是否依然爲8,如依然爲8,說明在我
 * 將8改爲9的過程之中沒有其他人來過,那就直接將9寫回去了,搞定。
 * 當然,如果將8讀來加1的過程之中又有人打斷了,有人將8改成100了,怎麼辦?那就把100讀過來加1,將101往回寫的過程中判斷
 * 原值是否依然是100....一直到某一次成功了爲止。
 * 你會發現它就在這裏不停的循環,讀取當前值,計算結果,比較當前值和新值,如果當前值和新值相等,更新爲新值。如果不相等,
 * 就再來一遍,總有一次能成功。
 *
 * CAS的ABA問題:
 * 上面的這段話描述裏面有個很重要的問題,此0非彼0的問題,線程1把0讀過來改成了1,往回寫的過程中發現原值依然爲0,但是
 * 這個0是不是你所看到的那個0呢?未必,有可能在這個過程之中,這個0被別的線程改成了8,又被別的線程改回了0!中間有個
 * 0->8->0的過程,此0非彼0,A->B->A,這就是ABA問題。
 * 但是咱們這個程序是不存在這個問題,只是理論上有,簡單數據類型就算ABA問題,但是對我而言沒關係,這種可以不在乎,略過。
 * 但是在有些情況下,是要解決的。如果這個值是一個引用的話,讀過來引用值,對它的屬性進行了一些修改,它是一個對象,當你再
 * 往回寫的時候,有可能這個引用指向的對象裏面的內容發生了改變,引用依然還是這個引用,但是裏面的內容發生了改變,這時就要
 * 在乎這個ABA問題了。
 *
 * 解決ABA問題:
 * 加version,你的女朋友,你早上離開了她,晚上回來發現依然是她,但此她還是彼她嘛?你比較懷疑,怎麼辦?在她腦門上寫1.0,你就
 * 走了,回來的時候依然是她,不過如果中間經過任何其他線程操作,這個ver1.0都會加1。這時候你發現她腦門上是99.0,那肯定中間經過
 * 了改變,當然就看你在乎不在乎。所以加version,加版本就可以解決。
 *
 * CAS的底層原子性保障:
 * 除了ABA問題,CAS還有一個巨大問題。分析一下:
 * 線程1將0讀過來改成1,把1往回寫的過程中是一個CAS操作。CAS叫compare and swap,compare and set,僞代碼就是:
 * if(v==0){v=1},這個操作實際上底層就是兩步:比較和設定。那萬一當你執行完if(v==0)後,這個時間點上被另外線程打斷,
 * 另外線程把0改成8了,那線程1又將8->1,那還是出問題了,數據還是不一致!
 * 所以,如果想讓CAS產生作用的話,必須保證CAS操作本身必須是原子性的。
 *
 * 悲觀鎖和樂觀鎖的效率:
 * 不要認爲悲觀鎖的效率一定比樂觀鎖的效率高,CAS一定比上來就上鎖的效率高,不是的,不一定。
 * 悲觀鎖一般採用什麼樣的實現呢,一把鎖lock,和這把鎖關聯的隊列,這個隊列用來等待着這把鎖,比如說小明wc蹲着,這把
 * 鎖是他的,隊列裏還有小紅、小花、小光、小剛...他們在等着。在隊列裏排着隊,操作系統OS說小紅輪到你了,你出來,可以搶
 * 這把鎖了,凡是在隊列裏等待的這些線程,是不消耗CPU資源的。可是與此形成鮮明對比的是,如果這時候小明在這我們用的是樂觀鎖,
 * 線程會while循環不斷看小明有沒有出來,很多人,小紅、小花...這些人不會坐在那裏安安靜靜的等待,他們會領着褲子原地打轉。
 * 這些線程消耗cpu,都是活着的線程,cpu一個是一直要運行他們的while循環,一個是要進行他們的線程切換。而等待隊列裏的cpu是不佔用
 * cpu的,他們狀態是parking、waiting或blocked,什麼時候OS說輪到你了你再佔用cpu資源。所以樂觀鎖是要消耗cpu的,消耗的比悲觀鎖要
 * 多一些。
 * 使用場景:
 * 悲觀鎖:臨界區執行時間比較長,等的人很多
 * 樂觀鎖:時間短,等的人少
 * 還是沒有量化啊,實際中該使用悲觀鎖還是樂觀鎖呢?壓測,可以寫兩種比較一下時間,實際中決定。
 * 但是實戰中啊,就用synchronized,因爲synchronized現在做了一系列的優化,它內部既有自旋鎖,又有偏向鎖,又有重量級鎖進行
 * 自適應的升級過程,自動完成鎖升級,它的效率已經調試的很不錯了。
 *
 */
public class T00_IPlusPlus {
    private static long n = 0L;

    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[100];
        CountDownLatch latch = new CountDownLatch(threads.length);

        for(int i=0; i<threads.length; i++){
            threads[i] = new Thread(() -> {
               for(int j=0; j<10000; j++){
                   synchronized (T00_IPlusPlus.class) {
                       n++;
                   }
               }
               latch.countDown();
            });
        }

        for(Thread t : threads){
            t.start();
        }

        latch.await();

        System.out.println(n);
    }
}

 

 

二、AtomicXXX類:

package atomicxxx;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 這裏就是一種CAS機制
 * incrementAndGet每次往回寫的時候都要比較一下,看下是否原值是我期望的那個值。
 * incrementAndGet操作自帶原子性:
 * unsafe.getAndAddInt->this.compareAndSwapInt->native boolean compareAndSwapInt
 * 得益於CPU在彙編級別上支持指令:cmpxchg,但是cmpxchg不是原子性的,最終實現:
 * lock cmpxchg
 * 所以你會發現,CAS在宏觀上我們叫做自旋鎖,樂觀鎖,但它在底層上的實現,微觀上的實現實際上是一個悲觀鎖。
 */
public class T01_AtomicInteger {

    AtomicInteger count = new AtomicInteger(0);

    void m(){
        for(int i=0; i<10000; i++){
            count.incrementAndGet();
        }
    }

    public static void main(String[] args) {
        T01_AtomicInteger t = new T01_AtomicInteger();

        List<Thread> threads = new ArrayList<>();

        for(int i=0; i<100; i++){
            threads.add(new Thread(t::m, "thread-" + i));
        }

        threads.forEach(o -> o.start());
        threads.forEach(o -> {
            try {
                o.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        System.out.println(t.count);
    }
}

 

 

 

 

 

 

---

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章