<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          CAS 原子操作

          共 8559字,需瀏覽 18分鐘

           ·

          2020-10-07 17:00

          點擊上方藍(lán)色字體,選擇“標(biāo)星公眾號”

          優(yōu)質(zhì)文章,第一時間送達(dá)

          ? 作者?|??彼岸舞?

          來源 |? urlify.cn/MRn6rm

          66套java從入門到精通實戰(zhàn)課程分享

          理會CAS和CAS:

            有時候面試官面試問你的時候,會問,談?wù)勀銓AS的理解,這時應(yīng)該有很多人,就會比較懵,當(dāng)然,我也會比較懵,當(dāng)然我和很多人的懵不同,很多人可能,并不知道CAS是一個什么東西,而在我看來我是不知道他問的是那個CAS

            我一般會問面試官,問他問的CAS是"原子操作",還是"單點登錄"

            因為在JAVA并發(fā)中的原子操作是稱為CAS的,也就是英文單詞CompareAndSwap的縮寫,中文意思是:比較并替換。

            但是在企業(yè)應(yīng)用中CAS也被稱為企業(yè)級開源單點登錄解決方案,是 Central Authentication Service 的縮寫 —— 中央認(rèn)證服務(wù),一種獨立開放指令協(xié)議,是 Yale 大學(xué)發(fā)起的一個企業(yè)級開源項目,旨在為 Web 應(yīng)用系統(tǒng)提供一種可靠的 SSO 解決方案。

          CAS(Compare And Swap):

            我們先要學(xué)習(xí)的是并發(fā)編程中的CAS,也就是原子操作

            那么,什么是原子操作?如何實現(xiàn)原子操作?

          什么是原子操作:

            原子,也是最小單位,是一個不可再分割的單位,不可被中斷的一個或者一系列操作

            CAS是以一種無鎖的方式實現(xiàn)并發(fā)控制,在實際情況下,同時操作一個對象的概率非常小,所以多數(shù)加鎖操作做的基本是無用功

            CAS以一種樂觀鎖的方式實現(xiàn)并發(fā)控制

          如何實現(xiàn)原子操作:

            Java可以通過鎖和循環(huán)CAS的方式實現(xiàn)原子操作

          為什么要有CAS:  

            CAS就是比較并且替換的一個原子操作,在CPU的指令級別上進(jìn)行保證

            為什么要有CAS:

              Sync是基于阻塞的鎖的機制,

                1:被阻塞的線程優(yōu)先級很高

                2:拿到鎖的線程一直不釋放鎖則么辦

                3:大量的競爭,消耗CPU,同時帶來死鎖或者其他線程安全

              因為通過鎖實現(xiàn)原子操作時,其他線程必須等待已經(jīng)獲得鎖的線程運行完車之后才能獲取鎖,這樣就會占用系統(tǒng)大量資源

          CAS原理:

            從CPU指令級別保證這是一個原子操作

          CAS包含哪些參數(shù):

            三個運算符:

              一個內(nèi)存地址V

              一個期望的值A(chǔ)

              一個新值B

            基本思路:

              如果地址V上的值和期望的值A(chǔ)相等,就給地址V賦值新值B,如果不是,不做任何操作

            循環(huán)CAS:

              在一個(死)循環(huán)中[for(;;)]里不斷進(jìn)行CAS操作,直到成功為止(自旋操作即死循環(huán))

          CAS問題:

            ABA問題:

              那么什么是ABA問題?就是內(nèi)存中原本是A,然后通過CAS變成了B,然后再次通過CAS變成了A,這個過程中,相對于結(jié)果來說,是沒有任何改變的,但是相對于內(nèi)存來說,至少發(fā)生過兩次變化,這就是ABA問題

              生活中:

                就像你接了一杯水,這時水是滿的,但是這個時候,你的同時很渴,過來拿你的水直接喝掉了一半,這時水剩下了一半,接著,你的同事又重新把你的水幫你接滿了,那么這時你的水還是滿的,相對于水來說,他還是滿的,但是相對于杯子來說,他已經(jīng)被用過了兩次,一次是喝水,一次是接水,這就是ABA問題

              從Java1.5開始JDK的atomic包里提供了一個類AtomicStampedReference來解決ABA問題。這個類的compareAndSet方法作用是首先檢查當(dāng)前引用是否等于預(yù)期引用,并且當(dāng)前標(biāo)志是否等于預(yù)期標(biāo)志,如果全部相等,則以原子方式將該引用和該標(biāo)志的值設(shè)置為給定的更新值。

              生活中:

                你接了一杯水,然后旁邊放上一張登記表,這個時候你同事過來,直接喝掉了一半,然后登記上,XXX喝掉了一半的水,然后去給你接滿了,再次登記上,我給你接滿了,這時,ABA的問題就得到了解決,你一看這個表就知道了一切

            開銷問題:

              在自旋或者死循環(huán)中不斷進(jìn)行CAS操作,但是長期操作不成功,CPU不斷的循環(huán),帶來的開銷問題

              自旋CAS如果長時間不成功,會給CPU帶來非常大的執(zhí)行開銷。如果JVM能支持處理器提供的pause指令那么效率會有一定的提升,pause指令有兩個作用,第一它可以延遲流水線執(zhí)行指令(de-pipeline),使CPU不會消耗過多的執(zhí)行資源,延遲的時間取決于具體實現(xiàn)的版本,在一些處理器上延遲時間是零。第二它可以避免在退出循環(huán)的時候因內(nèi)存順序沖突(memory order violation)而引起CPU流水線被清空(CPU pipeline flush),從而提高CPU的執(zhí)行效率。

            只能保證一個共享變量的原子操作

              當(dāng)對一個共享變量執(zhí)行操作時,我們可以使用循環(huán)CAS的方式來保證原子操作,但是對多個共享變量操作時,循環(huán)CAS就無法保證操作的原子性,這個時候就可以用鎖,或者有一個取巧的辦法,就是把多個共享變量合并成一個共享變量來操作。比如有兩個共享變量i=2,j=a,合并一下ij=2a,然后用CAS來操作ij。從Java1.5開始JDK提供了AtomicReference類來保證引用對象之間的原子性,你可以把多個變量放在一個對象里來進(jìn)行CAS操作。

          CAS的目的:

            利用CPU的CAS指令,同時借助JNI來完成Java的非阻塞算法。其它原子操作都是利用類似的特性完成的。而整個J.U.C都是建立在CAS之上的,因此對于synchronized阻塞算法,J.U.C在性能上有了很大的提升。

          JDK中相關(guān)原子操作類的使用:

            更新基本類型類:AtomicBoolean,AtomicInteger,AtomicLong

            更新數(shù)組類:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArrat

            更新引用類型:

          AtomicReference,AtomicMarkableReference,AtomicStampedReference

            原子更新字段類:

          AtomicReferenceFieldUpdater,AtomicIntegerFieldUpdater,AtomicLongFieldUpdater

          理論已經(jīng)理解的差不多了,接下來寫寫代碼

          使用AtomicInteger

          package?org.dance.day3;

          import?java.util.concurrent.atomic.AtomicInteger;

          /**
          ?*?使用原子類int類型
          ?*?@author?ZYGisComputer
          ?*/
          public?class?UseAtomicInt?{

          ????static?AtomicInteger?atomicInteger?=?new?AtomicInteger(10);

          ????public?static?void?main(String[]?args)?{
          ????????//?10->11?10先去再增加
          ????????System.out.println(atomicInteger.getAndIncrement());
          ????????//?11->12?12先增加再取
          ????????System.out.println(atomicInteger.incrementAndGet());
          ????????//?獲取
          ????????System.out.println(atomicInteger.get());
          ????}

          }

          返回值:

          10
          12
          12

          通過返回值可以看到,第一個是先獲取返回值后累加1,第二個是先累加1后再返回,第三個是獲取當(dāng)前值

          使用AtomicIntegerArray

          package?org.dance.day3;

          import?java.util.concurrent.atomic.AtomicIntegerArray;

          /**
          ?*?使用原子類int[]
          ?*?@author?ZYGisComputer
          ?*/
          public?class?UseAtomicIntegerArray?{

          ????static?int[]?values?=?new?int[]{1,2};

          ????static?AtomicIntegerArray?atomicIntegerArray?=?new?AtomicIntegerArray(values);

          ????public?static?void?main(String[]?args)?{
          ????????//改變的第一個參數(shù)是?數(shù)組的下標(biāo),第二個是新值
          ????????atomicIntegerArray.getAndSet(0,3);
          ????????//?獲取原子數(shù)組類中的下標(biāo)為0的值
          ????????System.out.println(atomicIntegerArray.get(0));
          ????????//?獲取源數(shù)組中下標(biāo)為0的值
          ????????System.out.println(values[0]);
          ????}

          }

          返回結(jié)果:

          3
          1

          通過返回結(jié)果我們可以看到,源數(shù)組中的值并沒有改變,只有引用中的值發(fā)生了改變,這是則么回事?

          /**
          ?????*?Creates?a?new?AtomicIntegerArray?with?the?same?length?as,?and
          ?????*?all?elements?copied?from,?the?given?array.
          ?????*
          ?????*?@param?array?the?array?to?copy?elements?from
          ?????*?@throws?NullPointerException?if?array?is?null
          ?????*/
          ????public?AtomicIntegerArray(int[]?array)?{
          ????????//?Visibility?guaranteed?by?final?field?guarantees
          ????????this.array?=?array.clone();
          ????}

          通過看源碼我們得知他是調(diào)用了數(shù)組的克隆方法,克隆了一個一模一樣的

          使用AtomicReference

          package?org.dance.day3;

          import?java.util.concurrent.atomic.AtomicReference;

          /**
          ?*?使用原子類引用類型
          ?*?@author?ZYGisComputer
          ?*/
          public?class?UseAtomicReference?{

          ????static?AtomicReference?atomicReference?=?new?AtomicReference<>();

          ????public?static?void?main(String[]?args)?{

          ????????UserInfo?src?=?new?UserInfo("彼岸舞",18);

          ????????//?使用原子引用類包裝一下
          ????????atomicReference.set(src);

          ????????UserInfo?target?=?new?UserInfo("彼岸花",19);

          ????????//?這里就是CAS改變了,這個應(yīng)用類就好像一個容器也就是內(nèi)存V,而src就是原值A(chǔ),target就是新值B
          ????????//?期望原值是src,如果是的話,改變?yōu)閠arget,否則不變
          ????????atomicReference.compareAndSet(src,target);

          ????????System.out.println(atomicReference.get());

          ????????System.out.println(src);

          ????}

          ????static?class?UserInfo{
          ????????private?String?name;
          ????????private?int?age;

          ????????@Override
          ????????public?String?toString()?{
          ????????????return?"UserInfo{"?+
          ????????????????????"name='"?+?name?+?'\''?+
          ????????????????????",?age="?+?age?+
          ????????????????????'
          }';
          ????????}

          ????????public?UserInfo()?{
          ????????}

          ????????public?UserInfo(String?name,?int?age)?{
          ????????????this.name?=?name;
          ????????????this.age?=?age;
          ????????}

          ????????public?String?getName()?{
          ????????????return?name;
          ????????}

          ????????public?void?setName(String?name)?{
          ????????????this.name?=?name;
          ????????}

          ????????public?int?getAge()?{
          ????????????return?age;
          ????????}

          ????????public?void?setAge(int?age)?{
          ????????????this.age?=?age;
          ????????}
          ????}

          }

          返回結(jié)果:

          UserInfo{name='彼岸花',?age=19}
          UserInfo{name='彼岸舞',?age=18}

          通過返回結(jié)果可以直觀的看到,原子引用類中的值發(fā)生了改變,但是源對象src卻沒有改變,因為原子引用類和原對象本身是兩個東西,CAS后就可以理解為內(nèi)存中的東西變了,也可以說是引用變了,他只能保證你在改變這個引用的時候保證是原子性的

          記得之前上面說的ABA問題吧,在這里就是解決代碼

          JDK中提供了兩種解決ABA問題的類

            AtomicStampedReference

              AtomicStampedReference,里面是用int類型,他關(guān)心的是被人動過幾次

            AtomicMarkableReference

              AtomicMarkableReference,里面是用boolean類型,他只關(guān)心這個版本有沒有人動過

          ?兩個類關(guān)心的點不一樣,側(cè)重的方向不一樣,就像之前說的喝水問題,AtomicStampedReference關(guān)心的是,被幾個人動過,而AtomicMarkableReference關(guān)心的是有沒有人動過

          使用AtomicStampedReference解決ABA問題

          package?org.dance.day3;

          import?java.util.concurrent.atomic.AtomicStampedReference;

          /**
          ?*?使用版本號解決ABA問題
          ?*?@author?ZYGisComputer
          ?*/
          public?class?UseAtomicStampedReference?{

          ????/**
          ?????*?構(gòu)造參數(shù)地第一個是默認(rèn)值,第二個就是版本號
          ?????*/
          ????static?AtomicStampedReference?atomicStampedReference?=?new?AtomicStampedReference<>("src",0);

          ????public?static?void?main(String[]?args)?throws?InterruptedException?{

          ????????//?獲取初始版本號
          ????????final?int?oldStamp?=?atomicStampedReference.getStamp();

          ????????//?獲取初始值
          ????????final?String?oldValue?=?atomicStampedReference.getReference();

          ????????System.out.println("oldValue:"+oldValue+"?oldStamp:"+oldStamp);

          ????????Thread?success?=?new?Thread(new?Runnable()?{
          ????????????@Override
          ????????????public?void?run()?{
          ????????????????System.out.println(Thread.currentThread().getName()+",當(dāng)前變量值:"+oldValue+"當(dāng)前版本號:"+oldStamp);
          ????????????????//?變更值和版本號
          ????????????????/**
          ?????????????????*?第一個參數(shù):期望值
          ?????????????????*?第二個參數(shù):新值
          ?????????????????*?第三個參數(shù):期望版本號
          ?????????????????*?第四個參數(shù):新版本號
          ?????????????????*/
          ????????????????boolean?b?=?atomicStampedReference.compareAndSet(oldValue,?oldValue?+?"java",?oldStamp,?oldStamp?+?1);
          ????????????????System.out.println(b);
          ????????????}
          ????????});

          ????????Thread?error?=?new?Thread(new?Runnable()?{
          ????????????@Override
          ????????????public?void?run()?{
          ????????????????//?獲取原值
          ????????????????String?sz?=?atomicStampedReference.getReference();
          ????????????????int?stamp?=?atomicStampedReference.getStamp();
          ????????????????System.out.println(Thread.currentThread().getName()+",當(dāng)前變量值:"+sz+"當(dāng)前版本號:"+stamp);
          ????????????????boolean?b?=?atomicStampedReference.compareAndSet(oldValue,?oldValue?+?"C",?oldStamp,?oldStamp?+?1);
          ????????????????System.out.println(b);
          ????????????}
          ????????});

          ????????success.start();
          ????????success.join();
          ????????error.start();
          ????????error.join();
          ????????System.out.println(atomicStampedReference.getReference()+":"+atomicStampedReference.getStamp());
          ????}

          }

          返回結(jié)果:

          oldValue:src?oldStamp:0
          Thread-0,當(dāng)前變量值:src當(dāng)前版本號:0
          true
          Thread-1,當(dāng)前變量值:srcjava當(dāng)前版本號:1
          false
          srcjava:1

          通過返回結(jié)果可以觀察到,原始值是src,版本是0,然后使用join方法使我們的正確線程確保咋錯誤線程之前執(zhí)行完畢,當(dāng)正確線程執(zhí)行完畢后,會把值改為srcjava,版本改為+1,然后執(zhí)行錯誤的線程,錯誤的線程在嘗試去改值的時候,發(fā)現(xiàn)期望的值是src,但是值已經(jīng)被改變成srcjava了,并且期望的版本是0,但是版本已經(jīng)被改為1了,所以他無法修改,在兩個線程都執(zhí)行完畢之后,打印的值是?srcjava,版本是1,成功的解決了ABA問題,當(dāng)然在這里面我的期望值是還是src,也可以改為src+java但是因為版本不一樣也是無法修改成功的;親測沒問題

          原子更新字段類就不寫了,那個使用比較麻煩,如果多個字段的話,就直接使用AtomicReference類就可以了





          粉絲福利:108本java從入門到大神精選電子書領(lǐng)取

          ???

          ?長按上方鋒哥微信二維碼?2 秒
          備注「1234」即可獲取資料以及
          可以進(jìn)入java1234官方微信群



          感謝點贊支持下哈?

          瀏覽 33
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产人妻人伦精品一区 | 美女啪啪啪网站 | 99久久精品国产一区色 | 色婷在线视频 | 日韩成人精品 |