1. 程式人生 > 其它 >java--JUC--CAS問題及解決方式

java--JUC--CAS問題及解決方式

  1. CAS:
    1. package com.model.CAS;
      
      import java.util.concurrent.atomic.AtomicInteger;
      
      /**
       * @Description:測試類
       * @Author: 張紫韓
       * @Crete 2021/6/10 13:47
       */
      public class CASDemo {
          /**
           * 什麼是CAS: compareAndSet  比較並交換
           * 總結:比較當前工作記憶體的值和主記憶體的值,如果這個值是期望的,則執行操作,如果不是就一直迴圈(自旋鎖)
           * 缺點:
           * 1.迴圈耗時
           * 2.一次性只能保證一個共享變數的原子性
           * 3.會產生ABA問題
           * 
      */ public static void main(String[] args) { //原子類 java.until.concurrent.atomic AtomicInteger atomicInteger = new AtomicInteger(2021); //expect :期望 update:更新 //如果是2021 就改為2022並返回true,如果不是2021就不會更新為2022並返回false ,CAS是cpu的原語 System.out.println(atomicInteger.compareAndSet(2021, 2022)); //
      返回值為ture和false System.out.println(atomicInteger.get()); System.out.println(atomicInteger.compareAndSet(2021, 2022)); System.out.println(atomicInteger.get()); atomicInteger.getAndIncrement(); // 相當於是++ /** * atomicInteger.getAndIncrement(); 是如何實現的呢? * * 通過原始碼我們可以看到,他是通過呼叫 unsafe.getAndAddInt(this, valueOffset, 1)來實現加一的, * 我們在進入到 unsafe類中,他呼叫了更底層的compareAndSwapInt(var1, var2, var5, var5 + var4))進行加一 * 而public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5) * 類是由native標識的,是呼叫了底層的c語言庫 * * public final int getAndIncrement() { * return unsafe.getAndAddInt(this, valueOffset, 1); * } * * public final int getAndAddInt(Object var1, long var2, int var4) { * int var5; * do { * var5 = this.getIntVolatile(var1, var2); * } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); * * return var5; * } * *
      */ } }
  2. CAS帶來的ABA問題:
    1. package com.model.CAS;
      
      import java.util.concurrent.TimeUnit;
      import java.util.concurrent.atomic.AtomicInteger;
      import java.util.concurrent.atomic.AtomicStampedReference;
      
      /**
       * @Description:測試類
       * @Author: 張紫韓
       * @Crete 2021/6/10 14:13
       */
      public class CAS_ABA {
      
          /**
           * 當一個執行緒去獲取一個值得時候 :獲取到之後 A=1,在執行 compareAndSet(1,2)之前
           * 另一個執行緒也獲取到了A=1 ,然後快速的執行 compareAndSet(1,2),和compareAndSet(2,1)
           * 這是 第一個執行緒在執行 compareAndSet(1,2)時 他拿到的A=1已經不是最新的值,已經被其他的執行緒修改了
           * */
      
          public static void main(String[] args) {
              //解決ABA問題,使用原子引用,
              AtomicInteger atomicInteger = new AtomicInteger(2021);
      
      //        AtomicStampedReference<Integer> atomicInteger = new AtomicStampedReference<>(2020, 1);
      
      
              //搗亂的執行緒
              new Thread(() ->{
                  System.out.println(Thread.currentThread().getName()+"獲取到了"+atomicInteger.get());
                  try {
                      TimeUnit.SECONDS.sleep(2);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
                  System.out.print(Thread.currentThread().getName()+"修改了"+atomicInteger.compareAndSet(2021, 2022));
                  System.out.println(atomicInteger.get());
                  System.out.print(Thread.currentThread().getName()+"修改了"+atomicInteger.compareAndSet(2022, 2021));
                  System.out.println(atomicInteger.get());
                  System.out.println(Thread.currentThread().getName()+"修改完畢"+atomicInteger.get());
              },"B").start();
      
      
              new Thread(() ->{
                  System.out.println(Thread.currentThread().getName()+"獲取到了"+atomicInteger.get());
                  try {
                      TimeUnit.SECONDS.sleep(3);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
                  //被騙的執行緒
                  System.out.println(Thread.currentThread().getName()+"修改了"+atomicInteger.compareAndSet(2021, 2022));
                  System.out.println(Thread.currentThread().getName()+"修改完畢"+atomicInteger.get());
              },"A").start();
      
          }
      }
    2. ABA問題解決,引用原子引用,
      1.     
      2. CAS:方法中會有比較,比較時的出現的坑 ,不能超出包裝類的 賦值範圍

      3. package com.model.CAS;
        
        import java.util.concurrent.TimeUnit;
        import java.util.concurrent.atomic.AtomicInteger;
        import java.util.concurrent.atomic.AtomicStampedReference;
        
        /**
         * @Description:測試類
         * @Author: 張紫韓
         * @Crete 2021/6/10 14:13
         */
        public class CAS_ABA_JJ {
        
            /**
             * 當一個執行緒去獲取一個值得時候 :獲取到之後 A=1,在執行 compareAndSet(1,2)之前
             * 另一個執行緒也獲取到了A=1 ,然後快速的執行 compareAndSet(1,2),和compareAndSet(2,1)
             * 這是 第一個執行緒在執行 compareAndSet(1,2)時 他拿到的A=1已經不是最新的值,已經被其他的執行緒修改了
             * */
        
            public static void main(String[] args) {
                //解決ABA問題,使用原子引用,
                //大坑:AtomicStampedReference包裝類,注意,如果泛型是一個包裝類,注意物件的引用問題
        //          integer使用了物件快取機制,預設的範圍是-128 - 127,推薦使用靜態工廠模式的方法valueOf獲取物件,而不是new ,因為valueOf使用了快取,
        //        而new 一定會建立新的物件分配新的記憶體空間
                AtomicStampedReference<Integer> atomicInteger = new AtomicStampedReference<>(1, 1);
        
                //搗亂的執行緒
                new Thread(() ->{
        
                    int  stamp=atomicInteger.getStamp();  //獲得的版本號
                    System.out.println(Thread.currentThread().getName()+"獲得版本號"+stamp);
                    try {
                        TimeUnit.SECONDS.sleep(2);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.print(Thread.currentThread().getName()+"修改了"+atomicInteger.compareAndSet(1, 2,atomicInteger.getStamp(),atomicInteger.getStamp()+1));
        
                    System.out.println(Thread.currentThread().getName()+"修改完畢版本號是:"+atomicInteger.getStamp());
                    System.out.print(Thread.currentThread().getName()+"修改了"+atomicInteger.compareAndSet(2, 1,atomicInteger.getStamp(),atomicInteger.getStamp()+1));
        
                    System.out.println(Thread.currentThread().getName()+"修改完畢版本號是:"+atomicInteger.getStamp());
                },"B").start();
        
        
                new Thread(() ->{
        
                    System.out.println(Thread.currentThread().getName()+"獲取到版本號"+atomicInteger.getStamp());
                    try {
                        TimeUnit.SECONDS.sleep(3);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //被騙的執行緒
                    System.out.println(Thread.currentThread().getName()+"修改了"+atomicInteger.compareAndSet(2021, 2022,atomicInteger.getStamp(),atomicInteger.getStamp()+1));
        
                    System.out.println(Thread.currentThread().getName()+"修改完畢"+atomicInteger.getStamp());
                },"A").start();
        
            }
        }