1. <ul id="0c1fb"></ul>

      <noscript id="0c1fb"><video id="0c1fb"></video></noscript>
      <noscript id="0c1fb"><listing id="0c1fb"><thead id="0c1fb"></thead></listing></noscript>

      99热在线精品一区二区三区_国产伦精品一区二区三区女破破_亚洲一区二区三区无码_精品国产欧美日韩另类一区

      RELATEED CONSULTING
      相關咨詢
      選擇下列產品馬上在線溝通
      服務時間:8:30-17:00
      你可能遇到了下面的問題
      關閉右側工具欄

      新聞中心

      這里有您想知道的互聯網營銷解決方案
      CAS是什么?

      一、什么是CAS

      CAS指CompareAndSwap,CAS是支持并發(fā)的第一個處理器提供原子的測試并設置操作,通常在單位上運行這項操作。

      專注于為中小企業(yè)提供網站設計制作、網站建設服務,電腦端+手機端+微信端的三站合一,更高效的管理,為中小企業(yè)隴西免費做網站提供優(yōu)質的服務。我們立足成都,凝聚了一批互聯網行業(yè)人才,有力地推動了1000+企業(yè)的穩(wěn)健成長,幫助中小企業(yè)通過網站建設實現規(guī)模擴充和轉變。

      CAS 操作包含三個操作數 -- 內存位置(V)、預期原值(A)和新值(B)。如果內存位置的值與預期原值相匹配,那么處理器會自動將該位置值更新為新值。否則,處理器不做任何操作。無論哪種情況,它都會在 CAS 指令之前返回該位置的值。(在 CAS 的一些特殊情況下將僅返回 CAS 是否成功,而不提取當前值。)CAS 有效地說明了"我認為位置 V 應該包含值 A;如果包含該值,則將 B 放到這個位置;否則,不要更改該位置,只告訴我這個位置現在的值即可。"

      二、atomic包

      使用java.util.atomic包在java中CAS的實現。

      CAS是什么?

      這就是java.util.atomic包下的類,我們著重看AtomicInteger源碼(其他的都是一樣的思想實現的)

      CAS有什么缺點?

      CAS缺點

      CAS雖然很高效的解決原子操作,但是CAS仍然存在三大問題。ABA問題,循環(huán)時間長開銷大和只能保證一個共享變量的原子操作

      1.  ABA問題。因為CAS需要在操作值的時候檢查下值有沒有發(fā)生變化,如果沒有發(fā)生變化則更新,但是如果一個值原來是A,變成了B,又變成了A,那么使用CAS進行檢查時會發(fā)現它的值沒有發(fā)生變化,但是實際上卻變化了。ABA問題的解決思路就是使用版本號。在變量前面追加上版本號,每次變量更新的時候把版本號加一,那么A-B-A 就會變成1A-2B-3A。

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

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

      3. 只能保證一個共享變量的原子操作。當對一個共享變量執(zhí)行操作時,我們可以使用循環(huán)CAS的方式來保證原子操作,但是對多個共享變量操作時,循環(huán)CAS就無法保證操作的原子性,這個時候就可以用鎖,或者有一個取巧的辦法,就是把多個共享變量合并成一個共享變量來操作。比如有兩個共享變量i=2,j=a,合并一下ij=2a,然后用CAS來操作ij。

      2.1、走進AtomicInteger源碼
      public class AtomicInteger extends Number implements java.io.Serializable {
          private static final long serialVersionUID = 6214790243416807050L;
      
          // 使用Unsafe.compareAndSwapInt進行原子更新操作
          private static final Unsafe unsafe = Unsafe.getUnsafe();
          //value對應的存儲地址偏移量
          private static final long valueOffset;
      
          static {
              try {
                  //使用反射及unsafe.objectFieldOffset拿到value字段的內存地址偏移量,這個值是固定不變的
                  valueOffset = unsafe.objectFieldOffset
                      (AtomicInteger.class.getDeclaredField("value"));
              } catch (Exception ex) { throw new Error(ex); }
          }
      
          //volatile修飾的共享變量
          private volatile int value;
          //..........
          }

      上面的代碼其實就是為了初始化內存值對應的內存地址偏移量valueOffset,方便后續(xù)執(zhí)行CAS操作時使用。因為這個值一旦初始化,就不會更改,所以使用static final 修飾。

      我們可以看到value使用了volatile修飾,其中也說了volatile的語義。

      我們都知道如果進行value++操作,并發(fā)下是不安全的。上一篇中我們也通過例子證明了volatile只能保證可見性,不能保證原子性。因為value++本身不是原子操作,value++分了三步,先拿到value的值,進行+1,再賦值回value。

      2.2、compareAndSwapXxx

      我們先看一看AtomicInteger提供的CAS操作。

          /**
           * 原子地將value設置為update,如果valueOffset對應的值與expect相等時
           *
           * @param expect 期待值
           * @param update 更新值
           * @return 如果更新成功,返回true;在valueOffset對應的值與expect不相等時返回false
           */
          public final boolean compareAndSet(int expect, int update) {
              return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
          }

      我們已經知道CAS的原理,那來看看下面的測試。你知道輸出的結果是多少嗎?評論區(qū)給出你的答案吧。

      public class AtomicIntegerTest {
          public static void main(String[] args) {
              AtomicInteger atomicInteger = new AtomicInteger();
              atomicInteger.compareAndSet(0, 1);
              atomicInteger.compareAndSet(2, 1);
              atomicInteger.compareAndSet(1, 3);
              atomicInteger.compareAndSet(2, 4);
              System.out.println(atomicInteger.get());
          }
      }

      Unsafe提供了三個原子更新的方法。

      關于Unsafe類,因為java不支持直接操作底層硬件資源,如分配內存等。如果你使用unsafe開辟的內存,是不被JVM垃圾回收管理,需要自己管理,容易造成內存泄漏等。

      2.3、AtomicInteger的原子自增方法

      我們上面說了,value++不是原子操作,不能在并發(fā)下使用。我們來看看AtomicInteger提供的原子++操作。

          /**
           * 原子地對value進行+1操作
           *
           * @return 返回更新后的值
           */
          public final int incrementAndGet() {
              return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
          }
      
          /**
           * unsafe提供的方法
           * var1 更改的目標對象
           * var2 目標對象的共享字段對應的內存地址偏移量valueOffset
           * var4 需要在原value上增加的值
           * @return 返回未更新前的值
           */
           public final int getAndAddInt(Object var1, long var2, int var4) {
              //期待值
              int var5;
              do {
                  //獲取valueOffset對應的value的值,支持volatile load
                  var5 = this.getIntVolatile(var1, var2);
                  //如果原子更新失敗,則一直重試,直到成功。
              } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
      
              return var5;
          }

      我們看到CAS只能原子的更新一個值,如果我們要原子更新多個值,CAS可以做到嗎?答案是可以的。

      2.4、AtomicReference

      如果要原子地更新多個值,就需要使用AtomicReference。其使用的是compareAndSwapObject方法。可以將多個值封裝到一個對象中,原子地更換對象來實現原子更新多個值。

      public class MultiValue {
          private int value1;
          private long value2;
          private Integer value3;
      
          public MultiValue(int value1, long value2, Integer value3) {
              this.value1 = value1;
              this.value2 = value2;
              this.value3 = value3;
          }
       }
      
      public class AtomicReferenceTest {
          public static void main(String[] args) {
              MultiValue multiValue1 = new MultiValue(1, 1, 1);
              MultiValue multiValue2 = new MultiValue(2, 2, 2);
              MultiValue multiValue3 = new MultiValue(3, 3, 3);
              AtomicReference atomicReference = new AtomicReference<>();
              //因為構造AtomicReference時,沒有使用有參構造函數,所以value默認值是null
              atomicReference.compareAndSet(null, multiValue1);
              System.out.println(atomicReference.get());
              atomicReference.compareAndSet(multiValue1, multiValue2);
              System.out.println(atomicReference.get());
              atomicReference.compareAndSet(multiValue2, multiValue3);
              System.out.println(atomicReference.get());
          }
      }
      //輸出結果
      //MultiValue{value1=1, value2=1, value3=1}
      //MultiValue{value1=2, value2=2, value3=2}
      //MultiValue{value1=3, value2=3, value3=3}

      我們再看一看AtomicReference的compareAndSet方法。

      注意:這里的比較都是使用==而非equals方法。所以最好封裝的MultiValue不要提供set方法。

       public final boolean compareAndSet(V expect, V update) {
           return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
       }
      2.5、CAS的ABA問題

      假設你的賬戶上有100塊錢,你要給女票轉50塊錢。

      我們使用CAS進行原子更新賬戶余額。由于某種原因,你第一次點擊轉賬出現錯誤,你以為沒有發(fā)起轉賬請求,這時候你又點擊了一次。系統(tǒng)開啟了兩個線程進行轉賬操作,第一個線程進行CAS比較,發(fā)現你的賬戶上預期是100塊錢,實際也有100塊錢,這時候轉走了50,需要設置為100 - 50 = 50 元,這時賬戶余額為50

      第一個線程操作成功了,第二個線程由于某種原因阻塞住了;這時候,你的家人又給你轉了50塊錢,并且轉賬成功。那你賬戶上現在又是100塊錢;

      太巧了,第二個線程被喚醒了,發(fā)現你的賬戶是100塊錢,跟預期的100是相等的,這時候又CAS為50。大兄弟,哭慘了,你算算,正確的場景你要有多少錢?這就是CAS存在的ABA問題。

      public class AtomicIntegerABA {
      
          private static AtomicInteger atomicInteger = new AtomicInteger(100);
      
          public static void main(String[] args) {
              ExecutorService executorService = Executors.newFixedThreadPool(3);
      
              //線程1
              executorService.execute(() -> {
                  System.out.println(Thread.currentThread().getName() + " - " + atomicInteger.get());
                  atomicInteger.compareAndSet(100, 50);
                  System.out.println(Thread.currentThread().getName() + " - " + atomicInteger.get());
              });
      
              //線程2
              executorService.execute(() -> {
                  try {
                      TimeUnit.MILLISECONDS.sleep(300);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
                  System.out.println(Thread.currentThread().getName() + " - " + atomicInteger.get());
                  atomicInteger.compareAndSet(50, 100);
                  System.out.println(Thread.currentThread().getName() + " - " + atomicInteger.get());
              });
      
              //線程3
              executorService.execute(() -> {
                  try {
                      TimeUnit.SECONDS.sleep(1);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
                  System.out.println(Thread.currentThread().getName() + " - " + atomicInteger.get());
                  atomicInteger.compareAndSet(100, 50);
                  System.out.println(Thread.currentThread().getName() + " - " + atomicInteger.get());
              });
      
              executorService.shutdown();
          }
      }
      //輸出結果
      //pool-1-thread-1 - 100
      //pool-1-thread-1 - 50
      //pool-1-thread-2 - 50
      //pool-1-thread-2 - 100
      //pool-1-thread-3 - 100
      //pool-1-thread-3 - 50

      大家心想,靠,這不是坑嗎?那還用。。。。。。。。。。。。。。冷靜,冷靜。你能想到的問題,jdk都能想到。atomic包提供了一個AtomicStampedReference

      2.6、AtomicStampedReference

      看名字是不是跟AtomicReference很像啊,其實就是在AtomicReference上加上了一個版本號,每次操作都對版本號進行自增,那每次CAS不僅要比較value,還要比較stamp,當且僅當兩者都相等,才能夠進行更新。

      public AtomicStampedReference(V initialRef, int initialStamp) {
              pair = Pair.of(initialRef, initialStamp);
          }
      //定義了內部靜態(tài)內部類Pair,將構造函數初始化的值與版本號構造一個Pair對象。
      private static class Pair {
              final T reference;
              final int stamp;
              private Pair(T reference, int stamp) {
                  this.reference = reference;
                  this.stamp = stamp;
              }
              static  Pair of(T reference, int stamp) {
                  return new Pair(reference, stamp);
              }
          }
      
      //所以我們之前的value就對應為現在的pair
          private volatile Pair pair;

      讓我們來看一看它的CAS方法。

       public boolean compareAndSet(V   expectedReference,
                                       V   newReference,
                                       int expectedStamp,
                                       int newStamp) {
              Pair current = pair;
              return
                  //只有在舊值與舊版本號都相同的時候才會更新為新值,新版本號
                  expectedReference == current.reference &&
                  expectedStamp == current.stamp &&
                  ((newReference == current.reference &&
                    newStamp == current.stamp) ||
                   casPair(current, Pair.of(newReference, newStamp)));
          }
      private boolean casPair(Pair cmp, Pair val) {
              return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
          }

      還是上面轉賬的例子,我們使用AtomicStampedReference來看看是否解決了呢。

      public class AtomicStampedReferenceABA {
          /**
           * 初始化賬戶中有100塊錢,版本號對應0
           */
          private static AtomicStampedReference atomicInteger = new AtomicStampedReference<>(100, 0);
      
          public static void main(String[] args) {
              ExecutorService executorService = Executors.newFixedThreadPool(3);
              int[] result = new int[1];
              //線程1
              executorService.execute(() -> {
                  System.out.println(Thread.currentThread().getName() + " - " + atomicInteger.get(result));
                  //將100更新為50,版本號+1
                  atomicInteger.compareAndSet(100, 50, 0, 1);
                  System.out.println(Thread.currentThread().getName() + " - " + atomicInteger.get(result));
              });
      
              //線程2
              executorService.execute(() -> {
                  try {
                      TimeUnit.MILLISECONDS.sleep(300);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
                  System.out.println(Thread.currentThread().getName() + " - " + atomicInteger.get(result));
                  //將50更新為100,版本號+1
                  atomicInteger.compareAndSet(50, 100, 1, 2);
                  System.out.println(Thread.currentThread().getName() + " - " + atomicInteger.get(result));
              });
      
              //線程3
              executorService.execute(() -> {
                  try {
                      TimeUnit.SECONDS.sleep(1);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
                  System.out.println(Thread.currentThread().getName() + " - " + atomicInteger.get(result));
                  //此線程還是以為沒有其他線程進行過更改,所以舊版本號還是0
                  atomicInteger.compareAndSet(100, 50, 0, 1);
                  System.out.println(Thread.currentThread().getName() + " - " + atomicInteger.get(result));
              });
      
              executorService.shutdown();
          }
      }
      //輸出結果
      //pool-1-thread-1 - 100
      //pool-1-thread-1 - 50
      //pool-1-thread-2 - 50
      //pool-1-thread-2 - 100
      //pool-1-thread-3 - 100
      //pool-1-thread-3 - 100

      網站題目:CAS是什么?
      文章路徑:http://www.ef60e0e.cn/article/ggojdh.html
      99热在线精品一区二区三区_国产伦精品一区二区三区女破破_亚洲一区二区三区无码_精品国产欧美日韩另类一区
      1. <ul id="0c1fb"></ul>

        <noscript id="0c1fb"><video id="0c1fb"></video></noscript>
        <noscript id="0c1fb"><listing id="0c1fb"><thead id="0c1fb"></thead></listing></noscript>

        曲阳县| 新丰县| 闽清县| 哈巴河县| 巴林左旗| 河曲县| 平凉市| 家居| 福州市| 麦盖提县| 临城县| 金华市| 从化市| 武功县| 桐乡市| 资溪县| 同德县| 东源县| 天柱县| 衡阳县| 裕民县| 潜江市| 洱源县| 集贤县| 高唐县| 剑河县| 晋宁县| 武宣县| 阿拉善左旗| 石首市| 灵川县| 斗六市| 额尔古纳市| 梧州市| 福贡县| 中牟县| 交城县| 固始县| 师宗县| 成都市| 民乐县|