我知道乐观锁,但是我的确不知道CAS啊,到底什么是CAS
目錄
寫在前面
一、初識CAS(比較并交換)
二、CAS原理(自旋鎖、unsafe類)
三、CAS是什么
四、CAS缺點
五、ABA問題
寫在前面
? ? 相信很多小伙伴對樂觀鎖、悲觀鎖都不陌生,但是說到java的cas,就蒙圈了。
? ? 那么到底什么是CAS呢?
一、初識CAS(比較并交換)
/** * CAS */ public class CASDemo {public static void main(String[] args) {AtomicInteger atomicInteger = new AtomicInteger(5);/* compareAndSet(int expect, int update)兩個參數 期望值,修改值假如說主物理內存的值與expect的值相等,則修改成功,否則修改失敗。*/System.out.println(atomicInteger.compareAndSet(5, 2021) + "現在值是" + atomicInteger.get());/*因為主物理內存的值已經變成了2021,再由5修改為2022,會執行失敗返回false*/System.out.println(atomicInteger.compareAndSet(5, 2022) + "現在值是" + atomicInteger.get());} }二、CAS原理(自旋鎖、unsafe類)
1.之前解決i++不安全的問題,用到了一個這個方法:
public final int getAndIncrement() {// this:當前對象。// valueOffset:內存偏移量(內存地址)// 1 加1return unsafe.getAndAddInt(this, valueOffset, 1); }2.源碼:
3.源碼解析
①Unsafe
? ? 是CAS的核心類,由于Java方法無法直接訪問底層系統,需要通過本地(native)方法來訪問,Unsafe相當于一個后門,基于該類可以直接操作特定內存的數據。Unsafe類存在于sun.misc包中,其內部方法操作可以像C的指針一樣直接操作內存,因為Java中CAS操作的執行依賴于Unsafe類的方法。
? ? 注意Unsafe類中的所有方法都是native修飾的,也就是說Unsafe類中的方法都直接調用操作系統底層資源執行相應任務。
②變量valueOffset,表示該變量值在內存中的偏移地址,因為Unsafe就是根據內存偏移地址獲取數據的。
③變量value用volatile修飾,保證了多線程之間的內存可見性。
三、CAS是什么
1.? ? CAS的全稱為Conpare-And-Swap,它是一條CPU并發原語。
? ? 它的功能是判斷內存某個位置的值是否為預期值,如果是則更改為新的值,這個過程是原子的。
? ? CAS并發原語體現在Java語言中就是sun.misc.Unsafe類中的各個方法。調用UnSafe類中的CAS方法,JVM會幫我們實現出CAS匯編指令。這是一種完全依賴于硬件的功能,通過它實現了原子操作。再次強調,由于CAS是一種系統原語,原語屬于操作系統用語范疇,是由若干條指令組成的,用于完成某個功能的一個過程,并且原語的執行必須是連續的,在執行過程中不允許被中斷,也就是說CAS是一條CPU的原子指令,不會造成所謂我的數據不一致問題。
2.源碼解讀:
//一直循環,1.先得到當前數據,2.再進行比較并交換,3.如果交換成功就退出,交換失敗就重新比較交換,直至成功。4.返回的是原值相當于i++
3.var解釋:
var1:AtomicInteger對象本身。
var2:該對象值的引用地址。
var4:需要變動的數量。
var5:是用var1?var2找出的主內存中真實的值。
用該對象當前的值與var5比較:
如果相同,更新var5+var4并且返回true,如果不同,繼續取值然后比較,直到更新完成。?? ?
為什么用CAS而不用synchronized?
synchronized加鎖,每次只有一個線程能訪問資源。并發性下降。
CAS使用自旋鎖,不需要手動加鎖。
4.CAS線程時沖突執行步驟:假設線程A和線程B兩個線程同時執行getAndAddInt操作(分別跑在不同CPU上):
? ? 1.AtomicInteger里面的value原始值為3,即主內存中AtomicInteger的value為3,根據JMM模型,線程A和線程B各自持有一份值為3的value的副本分別到各自的工作內存。
? ? 2.線程A通過getIntVolatile(var1, var2)拿到value值3,這時線程A被掛起。
? ? 3.線程B也通過getIntVolatile(var1, var2)方法獲取到value值3,此時剛好線程B沒有被掛起并執行compareAbdSwapInt方法比較內存值也為3,成功修改內存值為4,線程B打完收工一切OK。
? ? 4.此時線程A恢復,執行compareAndSwapInt方法比較,發現自己手里的值3和主內存的值4不一致,說明該值已經被其他線程搶先一步修改過了,那A線程本次修改失敗,只能重新讀取重新來一遍了。
? ? 5.線程A重新獲取value值,因為變量value被volatile修飾,所以其它線程對它的修改,線程A總是能夠看到,線程A繼續執行compareAndSwapInt進行比較交換,直到成功。
5.
6.CAS小總結
? ? CAS(CompareAndSwap)比較當前工作內存中的值和主內存中的值,如果相同則執行規定操作,否則繼續比較直到主內存和工作內存中的值一致為止。
? ? CAS應用:CAS有3個操作數,內存值V,舊的預期值A,要修改的更新值B。當且僅當預期值A和內存值V相同時,將內存值V修改為B,否則什么都不做。
四、CAS缺點
1.循環時間長,開銷大
? ? 我們可以看到getAndAddInt方法執行時,有個do-while。
? ? 如果CAS失敗,會一直進行嘗試。如果CAS長時間一直不成功,可能會給CPU帶來很大的開銷。
2.只能保證一個共享變量的原子操作。
? ? 當對一個共享變量執行操作時,我們可以使用循環CAS的方式來保證原子操作,但是,對多個共享變量操作時,循環CAS就無法保證操作的原子性,這個時候就可以用鎖來保證原子性。
3.引出來ABA問題。
五、ABA問題
1.ABA問題是怎么產生的
? ? CAS會導致“ABA問題”。
? ? CAS算法實現一個重要前提需要取出內存中某時刻的數據并在當下時刻比較并替換,那么在這個時間差類會導致數據的變化。
? ? 比如說一個線程1從內存位置V中取出A,這時候另一個線程2也從內存中取出A,并且線程2進行了一些操作將值變成了B,然后線程2又將值變成A。此時線程1進行CAS操作發現內存中仍然是A,然后線程1操作成功。
? ? 盡管線程1的CAS操作成功,但是不代表這個過程就是沒有問題的。
? ? 中間過程如果不介意別人動過,那無所謂。
? ? 中間過程別人不能動,那就有問題了。
2.解決ABA問題(加版本號)
①原子引用
import java.util.concurrent.atomic.AtomicReference; /** * 原子引用 * 如果想包裝某個類,就用原子引用 */ public class AtomicReferenceDemo {public static void main(String[] args) {User u1 = new User("zhangsan", 14);User u2 = new User("lisi", 15);AtomicReference<User> atomicReference = new AtomicReference<>();atomicReference.set(u1);// true 設置為lisiSystem.out.println(atomicReference.compareAndSet(u1, u2) + "當前值" +atomicReference.get().getName());// false 還是lisiSystem.out.println(atomicReference.compareAndSet(u1, u2) + "當前值" +atomicReference.get().getName());} }class User{private String name;private Integer age;public User(String name, Integer age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;} }②時間戳的原子引用?? ?ABA問題的徹底解決
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicStampedReference;/** * ABA問題的解決 */ public class ABADemo {static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);// 初始化值、版本號static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);public static void main(String[] args) {// 1.以下是ABA問題的產生new Thread(() ->{atomicReference.compareAndSet(100, 101);atomicReference.compareAndSet(101, 100);}, "t1").start();new Thread(() -> {try {// t2線程暫停1秒,確保t1線程完成了ABA操作Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}// 只認值,不認是否經歷過ABASystem.out.println(atomicReference.compareAndSet(100, 2019)+ ",t2當前值" + atomicReference.get());}, "t2").start();// 2.以下是ABA問題的解決new Thread(() -> {int stamp = atomicStampedReference.getStamp();// 版本號System.out.println("t3版本號" + stamp);// 暫停1秒鐘t3線程,確保t4拿到相同的版本號try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}// 期望值、修改值、目前版本號、修改后版本號atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);}, "t3").start();new Thread(() -> {int stamp = atomicStampedReference.getStamp();// 版本號System.out.println("t4版本號" + stamp);// 暫停3秒鐘t4線程,確保t3完成ABAtry {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}// 因為版本號已經被t3修改,所以這里比較并替換失敗boolean b = atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1);System.out.println(b + ",t4" + atomicStampedReference.getStamp() + "," + atomicStampedReference.getReference());}, "t4").start();} }總結
以上是生活随笔為你收集整理的我知道乐观锁,但是我的确不知道CAS啊,到底什么是CAS的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: volatile超详细讲解
- 下一篇: ArrayList不是并发安全的?那么在