日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

      歡迎訪問 生活随笔!

      生活随笔

      當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

      编程问答

      AtomicStampedReference源码分析

      發布時間:2023/11/30 编程问答 51 豆豆
      生活随笔 收集整理的這篇文章主要介紹了 AtomicStampedReference源码分析 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

      之前的文章已經介紹過CAS的操作原理,它雖然能夠保證數據的原子性,但還是會有一個ABA的問題。

      ????那么什么是ABA的問題呢?假設有一個共享變量“num”,有個線程A在第一次進行修改的時候把num的值修改成了33。修改成功之后,緊接著又立刻把“num”的修改回了22。另外一個線程B再去修改這個值的時候并不能感知到這個值被修改過。

      ????換句話說,別人把你賬戶里面的錢拿出來去投資,在你發現之前又給你還了回去,那這個錢還是原來的那個錢嗎?你老婆出軌之后又回到了你身邊,還是你原來的那個老婆嗎?

      ????為了模擬ABA的問題,我啟動了兩個線程訪問一個共享的變量。將下面的代碼拷貝到編譯器中,運行進行測試:

      public class ABATest {private final static AtomicInteger num = new AtomicInteger(100);public static void main(String[] args) {new Thread(() -> {num.compareAndSet(100, 101);num.compareAndSet(101, 100);System.out.println(Thread.currentThread().getName() + " 修改num之后的值:" + num.get());}).start();new Thread(() -> {try {TimeUnit.SECONDS.sleep(3);num.compareAndSet(100, 200);System.out.println(Thread.currentThread().getName() + " 修改num之后的值:" + num.get());} catch (InterruptedException e) {e.printStackTrace();}}).start();} }


      ????第一個線程先進行修改把數值從100修改為101,然后在從101修改回100,這個過程其實是發成了ABA的操作。第二個線程等待3秒(為了是讓第一個線程執行完畢,第二個線程在執行)之后進行值從100修改為200。按照我們的理解,第一個線程已經修改過原來的值了,那么第二個線程就不應該修改成功。但是如果你運行下面的測試用例的話,你會發現它是可以進行修改成功的,請看運行結果:

      Thread-0 修改num之后的值:100 Thread-1 修改num之后的值:200

      ????雖然結果是符合我們的預期的:數值被成功地進行了修改,但是修改的過程卻是不符合我們的預期的。

      ????為了解決這個問題,我們可以在修改的時候附加上一個版本號,也就是第幾次修改。每次修改的時候把版本號帶上,如果版本號能夠對應的上的話就進行修改,如果對應不上的話就不允許進行修改。

      ????所以如果修改的時候帶上的版本號不一致的話是不能夠進行成功修改的。我們可以按照上面的原理自己進行版本號的封裝,但也許會比較麻煩。因此我們可以使用JDK給我們提供的一個已經封裝好的類“AtomicStampedReference”來進行我們數據的更新。我們來看看下面的這些例子:

      public class AtomicStampedReferenceTest {private final static AtomicStampedReference<Integer> stamp = new AtomicStampedReference<>(100, 1);public static void main(String[] args) {new Thread(() -> {System.out.println(Thread.currentThread().getName() + " 第1次版本號:" + stamp.getStamp());stamp.compareAndSet(100, 200, stamp.getStamp(), stamp.getStamp() + 1);System.out.println(Thread.currentThread().getName() + " 第2次版本號:" + stamp.getStamp());stamp.compareAndSet(200, 100, stamp.getStamp(), stamp.getStamp() + 1);System.out.println(Thread.currentThread().getName() + " 第2次版本號:" + stamp.getStamp());}).start();new Thread(() -> {try {TimeUnit.SECONDS.sleep(3);System.out.println(Thread.currentThread().getName() + " 第1次版本號:" + stamp.getStamp());stamp.compareAndSet(100, 400, stamp.getStamp(), stamp.getStamp() + 1);System.out.println(Thread.currentThread().getName() + " 獲取到的值:" + stamp.getReference());} catch (InterruptedException e) {e.printStackTrace();}}).start();} } Thread-0 第1次版本號:1 Thread-0 第2次版本號:2 Thread-0 第2次版本號:2 Thread-1 第1次版本號:2 Thread-1 獲取到的值:200

      ????也是啟動了兩個線程對共享變量進行修改,但是這次不同的是帶著版本號對共享變量進行的修改。下面將上面的例子進行拆解分析,研究下“AtomicStampedReference”到底為我們做了一些什么。

      ????首先分析共享變量的創建:構建了一個“AtomicStampedReference”對象,并且顯示的賦值了100和1。

      private final static AtomicStampedReference<Integer> stamp = new AtomicStampedReference<>(100, 1);
      • ?

      ????構造函數調用了下面的源碼:

      public AtomicStampedReference(V initialRef, int initialStamp) {pair = Pair.of(initialRef, initialStamp); }

      ????"initialRef"是初始值,也就是我們定義的100,“initialStamp”是我們顯示聲明的一個整形類型的版本號。只要在int的范圍內即可,但是不要太大了, 畢竟是int如果超了就會丟失精度問題。

      ????然后調用了“Pair.of(initialRef, initialStamp)”,繼續跟進源碼查看:

      ????通過觀察源碼可以發現類“Pair”是“AtomicStampedReference”類的一個靜態內部類,有兩個參數的構造函數,然后把我們傳遞進來的初始值和版本號進行賦值給“Pair”對象。可以注意到“pair”被關鍵字“volatile”修飾,也就保證了內存的可見性和禁止指令的重排序。因此如果“pair”發生了變化,那么所有持有其引用的信息都會進行相應的數據更新。

      ????到此為止,“AtomicStampedReference”對象初始化完畢,內部包含了一個“reference”值為100, “stamp”為1的“pair”靜態內部類。

      ????“stamp.getStamp()”目的是為了獲取當前的版本號,我們在初始化的時候顯示設置了一個值1,因此第一次獲取到的版本號就是1。

      public int getStamp() {return pair.stamp; }

      ????“stamp.compareAndSet(100, 200, stamp.getStamp(), stamp.getStamp() + 1);”是進行第一次CAS更新數據,這次更新的時候就帶著版本號去更新了。

      new Thread(() -> {System.out.println(Thread.currentThread().getName() + " 第1次版本號:" + stamp.getStamp());stamp.compareAndSet(100, 200, stamp.getStamp(), stamp.getStamp() + 1);System.out.println(Thread.currentThread().getName() + " 第2次版本號:" + stamp.getStamp());stamp.compareAndSet(200, 100, stamp.getStamp(), stamp.getStamp() + 1);System.out.println(Thread.currentThread().getName() + " 第2次版本號:" + stamp.getStamp()); }).start();

      ????還記得嗎?之前的CAS比較是需要傳遞一個期望值和更新的值(內存中的值,底層的方法會給我們封裝好 ):

      num.compareAndSet(100, 101);
      • 1

      ????而帶著版本號的CAS需要我們傳遞四個值,一個是期望值,一個是更新的值,還有兩個就是期望的時間戳和需要更新的時間戳:

      V expectedReference // 表示預期值 V newReference, // 表示要更新的值 int expectedStamp, // 表示預期的時間戳 int newStamp // 表示要更新的時間戳

      ????之后進行了預期值的判斷,

      總結

      以上是生活随笔為你收集整理的AtomicStampedReference源码分析的全部內容,希望文章能夠幫你解決所遇到的問題。

      如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。