CAS(Compare and Swap),即比較并替換,是用于實現多線程同步的原子指令,是用于實現多線程同步的原子指令。
執行函數:CAS(V,E,N)
其包含3個參數:
· V表示要更新的變量
· E表示預期值
· N表示新值
假定有兩個操作A和B,如果從執行A的線程來看,當另一個線程執行B時,要么將B全部執行完,要么完全不執行B,那么A和B對彼此來說是原子的。
實現原子操作可以使用鎖,鎖機制,滿足基本的需求是沒有問題的了,但是有的時候我們的需求并非這么簡單,我們需要更有效,更加靈活的機制,synchronized關鍵字是基于阻塞的鎖機制,也就是說當一個線程擁有鎖的時候,訪問同一資源的其它線程需要等待,直到該線程釋放鎖。
這里會有些問題:首先,如果被阻塞的線程優先級很高很重要怎么辦?其次,如果獲得鎖的線程一直不釋放鎖怎么辦?(這種情況是非常糟糕的)。還有一種情況,如果有大量的線程來競爭資源,那CPU將會花費大量的時間和資源來處理這些競爭,同時,還有可能出現一些例如死鎖之類的情況,最后,其實鎖機制是一種比較粗糙,粒度比較大的機制,相對于像計數器這樣的需求有點兒過于笨重。
實現原子操作還可以使用當前的處理器基本都支持CAS的指令,只不過每個廠家所實現的算法并不一樣,每一個CAS操作過程都包含三個運算符:一個內存地址V,一個期望的值A和一個新值B,操作的時候如果這個地址上存放的值等于這個期望的值A,則將地址上的值賦為新值B,否則不做任何操作。
CAS的基本思路就是,如果這個地址上的值和期望的值相等,則給其賦予新值,否則不做任何事兒,但是要返回原值是多少。循環CAS就是在一個循環里不斷的做cas操作,直到成功為止。
CAS是怎么實現線程的安全呢?語言層面不做處理,我們將其交給硬件—CPU和內存,利用CPU的多處理能力,實現硬件層面的阻塞,再加上volatile變量的特性即可實現基于原子操作的線程安全。
CPU指令對CAS的支持
或許我們可能會有這樣的疑問,假設存在多個線程執行CAS操作并且CAS的步驟很多,有沒有可能在判斷V和E相同后,正要賦值時,切換了線程,更改了值。造成了數據不一致呢?答案是否定的。
因為CAS是一種系統原語,原語屬于操作系統用語范疇,是由若干條指令組成的,用于完成某個功能的一個過程,并且原語的執行必須是連續的,在執行過程中不允許被中斷,也就是說CAS是一條CPU的原子指令,不會造成所謂的數據不一致問題。
悲觀鎖,樂觀鎖
說到CAS,不得不提到兩個專業詞語:悲觀鎖,樂觀鎖。我們先來看看什么是悲觀鎖,什么是樂觀鎖。
悲觀鎖顧名思義,就是比較悲觀的鎖,總是假設最壞的情況,每次去拿數據的時候都認為別人會修改,所以每次在拿數據的時候都會上鎖,這樣別人想拿這個數據就會阻塞直到它拿到鎖(共享資源每次只給一個線程使用,其它線程阻塞,用完后再把資源轉讓給其它線程)。傳統的關系型數據庫里邊就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。Java中synchronized和ReentrantLock等獨占鎖就是悲觀鎖思想的實現。
而樂觀鎖反之,總是假設最好的情況,每次去拿數據的時候都認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,可以使用版本號機制和CAS算法實現。樂觀鎖適用于多讀的應用類型,這樣可以提高吞吐量,像數據庫提供的類似于write_condition機制,其實都是提供的樂觀鎖。我們今天講的CAS就是樂觀鎖。
由于CAS操作屬于樂觀派,它總認為自己可以成功完成操作,當多個線程同時使用CAS操作一個變量時,只有一個會勝出,并成功更新,其余均會失敗,但失敗的線程并不會被掛起,僅是被告知失敗,并且允許再次嘗試,當然也允許失敗的線程放棄操作。基于這樣的原理,CAS操作即使沒有鎖,同樣知道其他線程對共享資源操作影響,并執行相應的處理措施。同時從這點也可以看出,由于無鎖操作中沒有鎖的存在,因此不可能出現死鎖的情況,也就是說無鎖操作天生免疫死鎖。
CAS 的優點
非阻塞的輕量級樂觀鎖, 通過CPU指令實現, 在資源競爭不激烈的情況下性能高, 相比synchronize重量級悲觀鎖, synchronize有復雜的加鎖, 解鎖和喚醒線程操作。