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

歡迎訪問 生活随笔!

生活随笔

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

编程问答

Atomic原子类常用方法总结(包含四大类型)

發布時間:2025/1/21 编程问答 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Atomic原子类常用方法总结(包含四大类型) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

基本介紹:

Atomic指一個操作不可中斷,即使在多線程情況下,一個操作一旦開始,就不會被其他線程干擾。如果多線程中僅需要Atomic原子類解決的事情,就不需要synchronized重量級鎖了。

原子類共四類:

  • 基本類型:使用原子的方式更新基本類型
    ? a. AtomicInteger整形原子類
    ? b. AtomicLong長整型原子類
    ? c. AtomicBoolean布爾原子類

  • 數組類型:使用原子的方式更新數組中某個元素
    ? a. AtomicIntegerArray:整形數組原子類
    ? b. AtomicLongArray:長整形數組原子類
    ? c. AtomicReferenceArray:引用類型數組原子類(即對應數組中存放的元素為對象形式)

  • 引用類型:使用原子的方式更新某個對象
    ? a. AtomicReference:引用類型原子類
    ? b. AtomicStampedReference:AtomicReference的擴展版,增加了一個參數stamp標記,這里是為了解決了AtomicInteger和AtomicLong的操作會出現ABA問題。
    ? c. AtomicMarkableReference :與AtomicStampedReference差不多,只不過第二個參數不是用的int作為標志,而用boolean類型做標記,具體用法看后面講解。

  • 對象的屬性修改類型:使用原子的方式更新某個類中某個字段
    ? a. AtomicIntegerFieldUpdater:原子更新整形字段的更新器
    ? b. AtomicLongFieldUpdater:原子更新長整形字段的更新器
    ? c. AtomicReferenceFieldUpdater:原子更新引用類型字段的更新器


  • 使用方法:

    一、基本類型原子類

    由于三種類的方法基本一樣,下面就以AtomicInteger 為例:

    public final int set() //設一個值
    public final int get() //獲取當前的值
    public final int getAndSet(int newValue)//獲取當前的值,并設置新的值
    public final int getAndIncrement()//獲取當前的值,并自增
    public final int getAndDecrement() //獲取當前的值,并自減
    public final int getAndAdd(int delta) //獲取當前的值,并加上預期的值
    boolean compareAndSet(int expect, int update) //如果當前值等于預期值,則以原子方式將該值設置為輸入值(update)
    public final void lazySet(int newValue) //最終設置為newValue,使用 lazySet 設置之后可能導致其他線程在之后的一小段時間內還是可以讀到舊的值。

    public class AtomicIntegerTest {public static void main(String[] args) {AtomicInteger a = new AtomicInteger(0);for (int i = 1; i < 5; i++) {a.getAndIncrement(); // a 自增,相當于 a ++}//獲取當前a的值System.out.println("AtomicInteger a從0自增4次結果為:"+ a.get());System.out.println("AtomicInteger 當前a為:"+ a.getAndDecrement() + ",并自減一次"); //a --//獲取當前a的值,并更新a為8System.out.println("AtomicInteger a當前值為:"+a.getAndSet(8)+",并更新a為8");//獲取當前a的值,并將a加6System.out.println("AtomicInteger a當前值為:"+a.getAndAdd(6)+",并將a加6");a.compareAndSet(12,9); //如果a=12,就把a更新為9,否則不進行操作System.out.println("AtomicInteger a當前值為:"+a.get());a.compareAndSet(14,9); //如果a=14,就把a更新為9,否則不進行操作System.out.println("AtomicInteger a當前值為:"+a.get());} }

    二、數組類型原子類

    由于三種類的方法基本一樣,下面就以AtomicIntegerArray 為例:

    public final int get(int i) //獲取 index=i 位置元素的值
    public final int set(int i, int newValue) //為 index=i 位置元素設新值
    public final int getAndSet(int i, int newValue) //返回 index=i 位置的當前的值,并將其設置為新值:newValue
    public final int getAndIncrement(int i) //獲取 index=i 位置元素的值,并讓該位置的元素自增
    public final int getAndDecrement(int i) //獲取 index=i 位置元素的值,并讓該位置的元素自減
    public final int getAndAdd(int i, int delta) //獲取 index=i 位置元素的值,并加上預期的值
    boolean compareAndSet(int i, int expect, int update) //如果index=i 位置的值等于預期值,則以原子方式將 index=i 位置的元素值設置為輸入值(update)
    public final void lazySet(int i, int newValue) //最終 將index=i 位置的元素設置為newValue,使用 lazySet 設置之后可能導致其他線程在之后的一小段時間內還是可以讀到舊的值。

    public class AtomicIntegerArrayTest {public static void main(String[] args) {int[] a = {1,1,1,1};AtomicIntegerArray arr = new AtomicIntegerArray(a);System.out.println("arr數組初始值為:" +arr.toString());for (int i = 0; i < 4; i++) {arr.getAndIncrement(i); //index = i位置上的值arr[i]自增,相當于 a[i] ++}System.out.println("arr數組每個元素都自增1后為:" +arr.toString());//獲取當前arr[1]的值System.out.println("arr[1]的值為:"+ arr.get(1));System.out.println("arr[2]當前值為:"+ arr.getAndDecrement(2) + ",并讓arr[2]自減一次"); //a[2]--//獲取當前a[2]的值,并更新a為8System.out.println("arr[2]當前值為:"+arr.getAndSet(2,8)+",并更新a[2]為8");//獲取當前a的值,并將a加6System.out.println("arr[2]當前值為:"+arr.getAndAdd(2,6)+",并將a[2]加6");arr.compareAndSet(2,12,9); //如果a[2]=12,就把a[2]更新為9,否則不進行操作System.out.println("arr[2]當前值為:"+arr.get(2));arr.compareAndSet(2,14,9); //如果a[2]=14,就把a[2]更新為9,否則不進行操作System.out.println("arr[2]當前值為:"+arr.get(2));} }

    三、引用原子類AtomicReference

    public class User {private String name;private int age;public User(String name, int age) {this.name = name;this.age = age;}public String getName() {return this.name;}public void setName(final String name) {this.name = name;}public int getAge() {return this.age;}public void setAge(final int age) {this.age = age;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", age=" + age +'}';} } public class AtomicReferenceTest {public static void main(String[] args) {AtomicReference<User> u = new AtomicReference<>();User user1 = new User("金廠長",35);User user2 = new User("木易阿婆",26);User user3 = new User("快樂的小lau",22);u.set(user1);// 查看當前對象并設為新對象user2System.out.println("當前對象為:"+u.getAndSet(user2)+",并設置新對象為user2");System.out.println("當前對象為:"+u.get());System.out.println("如果當前對象為user2,就把當前對象設為user3,否則不操作");u.compareAndSet(user2,user3); //如果當前對象為user2則把當前對象設為user3System.out.println("當前對象為:"+u.get());} }

    四、AtomicStampedReference與AtomicMarkableReference類

    ABA問題:簡單講就是多線程環境,2次讀寫中一個線程修改A->B,然后又B->A,另一個線程看到的值未改變,又繼續修改成自己的期望值。當然我們如果不關心過程,只關心結果,那么這個就是無所謂的ABA問題。

    • 為了解決ABA問題,偉大的java為我們提供了AtomicMarkableReference和AtomicStampedReference類,為我們解決了問題
    • AtomicStampedReference是利用版本戳的形式記錄了每次改變以后的版本號,這樣的話就不會存在ABA問題了,在這里我借鑒一下別人舉得例子

    舉個通俗點的例子,你倒了一杯水放桌子上,干了點別的事,然后同事把你水喝了又給你重新倒了一杯水,你回來看水還在,拿起來就喝,如果你不管水中間被人喝過,只關心水還在,這就是ABA問題。如果你是一個講衛生講文明的小伙子,不但關心水在不在,還要在你離開的時候水被人動過沒有,因為你是程序員,所以就想起了放了張紙在旁邊,寫上初始值0,別人喝水前麻煩先做個累加才能喝水。這就是AtomicStampedReference的解決方案。

    public class AtomicStampedReferenceTest {public static void main(String[] args) throws InterruptedException{final Integer init_Ref = 0, init_Stamp = 0;AtomicStampedReference<Integer> asr = new AtomicStampedReference<>(init_Ref,init_Stamp);System.out.println("init_Ref為:"+asr.getReference() + " ====== init_Stamp為:"+asr.getStamp());Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {Integer ref = asr.getReference();Integer stamp = asr.getStamp();//與前面AtomicReference的compareAndSet不同的是,增加了一個stamp標記比較,ref與stamp同時與// 當前的ref、stamp相同時才進行 + 操作System.out.println(ref + " ====== " + stamp + " ====== "+ asr.compareAndSet(ref, ref + 10, stamp, stamp + 1));}});Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {Integer ref = asr.getReference();// 當前的ref相同,但此時版本號不同,操作不執行返回falseSystem.out.println(ref + " ====== " + init_Stamp + " ====== "+ asr.compareAndSet(ref, ref + 10, init_Stamp, init_Stamp + 1));}});t1.start();t1.join(); //只是為了讓代碼有序執行t2.start();t2.join();System.out.println("最終的結果為:"+ asr.getReference() + " ====== " + asr.getStamp());} }


    注:以上AtomicStampedReference部分摘自參考文章1, 本來看到這部分就想放棄了,但是翻到了這篇文章,講的挺清晰的,就又繼續看下去了。

    可以看出,第一次ref、stamp都與輸入值相等,因此執行ref+10,和stamp+1,此時ref=10,stamp=1。第二個線程中輸入的ref與當前ref值相同,但是init_Stamp=0 與當前stamp=1 不等,因此不執行。總的來說就是除了對比ref,又增加了一個stamp來判斷到底操不操作。

    AtomicMarkableReference與AtomicStampedReference不同的是將int stamp改為了boolean類型的mark做標記。同樣的例子:

    public class AtomicMarkableReferenceTest {public static void main(String[] args) throws InterruptedException{final Integer init_Ref = 0;final Boolean init_Mark = false;AtomicMarkableReference<Integer> amr = new AtomicMarkableReference<>(init_Ref,init_Mark);System.out.println("init_Ref為:"+amr.getReference() + " ====== init_Mark為:"+amr.isMarked());Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {Integer ref = amr.getReference();Boolean mark = amr.isMarked();//與前面AtomicReference的compareAndSet不同的是,增加了一個stamp標記比較,ref與stamp同時與// 當前的ref、stamp相同時才進行 + 操作System.out.println(ref + " ====== " + mark + " ====== "+ amr.compareAndSet(ref, ref + 10, mark, true));}});Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {Integer ref = amr.getReference();// 當前的ref相同,但此時版本號不同,操作不執行返回falseSystem.out.println(ref + " ====== " + init_Mark + " ====== "+ amr.compareAndSet(ref, ref + 10, init_Mark, true));}});t1.start();t1.join(); //只是為了讓代碼有序執行t2.start();t2.join();System.out.println("最終的結果為:"+ amr.getReference() + " ====== " + amr.isMarked());} }

    五、對象屬性修改類型

    以AtomicIntegerFieldUpdater 為例介紹一下簡單使用方法:

    public class User {private String name;volatile int age;public User(String name, int age) {this.name = name;this.age = age;}public String getName() {return this.name;}public void setName(final String name) {this.name = name;}public int getAge() {return this.age;}public void setAge(final int age) {this.age = age;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", age=" + age +'}';} } public class AtomicIntegerFieldUpdaterTest {public static void main(String[] args) {User user = new User("菜雞",28);AtomicIntegerFieldUpdater<User> u = AtomicIntegerFieldUpdater.newUpdater(User.class,"age");u.addAndGet(user,5);System.out.println(user.toString());} }


    這里值得注意的是:使用AtomicIntegerFieldUpdater.newUpdater修改屬性時:

  • 被修改的屬性必須是volatile類型的,在線程之間共享變量時保證立即可見,
  • 屬性的修飾符(public/protected/default/private)要保證當前操作對該屬性可以直接進行,比如當我們用private volatile int age 時就會報錯,因為private修飾時,外部無法訪問也無法修改。
  • 只能是實例變量,不能是類變量,也就是說不能加static關鍵字。
  • 只能是可修改變量,不能使final變量,因為final的語義就是不可修改。
  • 對于AtomicIntegerFieldUpdater和AtomicLongFieldUpdater只能修改int/long類型的字段,不能修改其包裝類型(Integer/Long)。如果要修改包裝類型就需要使用AtomicReferenceFieldUpdater。
  • 具體分析可以參考:參考文章3


    參考文章:
  • https://blog.csdn.net/zhaozhirongfree1111/article/details/72781758
  • https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484834&idx=1&sn=7d3835091af8125c13fc6db765f4c5bd&source=41#wechat_redirect
  • https://blog.csdn.net/zhaozhirongfree1111/article/details/72781147

  • 總結

    以上是生活随笔為你收集整理的Atomic原子类常用方法总结(包含四大类型)的全部內容,希望文章能夠幫你解決所遇到的問題。

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