AtomicInteger中的方法有哪些

這篇文章主要介紹“AtomicInteger中的方法有哪些”,在日常操作中,相信很多人在AtomicInteger中的方法有哪些問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”AtomicInteger中的方法有哪些”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!

10年的月湖網(wǎng)站建設(shè)經(jīng)驗,針對設(shè)計、前端、開發(fā)、售后、文案、推廣等六對一服務(wù),響應(yīng)快,48小時及時工作處理。全網(wǎng)營銷推廣的優(yōu)勢是能夠根據(jù)用戶設(shè)備顯示端的尺寸不同,自動調(diào)整月湖建站的顯示方式,使網(wǎng)站能夠適用不同顯示終端,在瀏覽器中調(diào)整網(wǎng)站的寬度,無論在任何一種瀏覽器上瀏覽網(wǎng)站,都能展現(xiàn)優(yōu)雅布局與設(shè)計,從而大程度地提升瀏覽體驗。創(chuàng)新互聯(lián)建站從事“月湖網(wǎng)站設(shè)計”,“月湖網(wǎng)站推廣”以來,每個客戶項目都認真落實執(zhí)行。

了解 AtomicInteger

AtomicInteger 是 JDK1.5 新添加的工具類,我們首先來看一下它的繼承關(guān)系AtomicInteger中的方法有哪些

與 int 的包裝類 Integer 一樣,都是繼承于 Number 類的。

AtomicInteger中的方法有哪些

這個 Number 類是基本數(shù)據(jù)類型的包裝類,一般和數(shù)據(jù)類型有關(guān)的對象都會繼承于 Number 類。

它的繼承體系很簡單,下面我們來看一下它的基本屬性和方法

AtomicInteger 的基本屬性

AtomicInteger 的基本屬性有三個

AtomicInteger中的方法有哪些

Unsafe 是 sun.misc 包下面的類,AtomicInteger 主要是依賴于 sun.misc.Unsafe 提供的一些 native  方法保證操作的原子性。

Unsafe 的 objectFieldOffset  方法可以獲取成員屬性在內(nèi)存中的地址相對于對象內(nèi)存地址的偏移量。說得簡單點就是找到這個變量在內(nèi)存中的地址,便于后續(xù)通過內(nèi)存地址直接進行操作,這個值就是  value這個我們后面會再細說

value 就是 AtomicIneger 的值。

AtomicInteger 的構(gòu)造方法

繼續(xù)往下看,AtomicInteger 的構(gòu)造方法只有兩個,一個是無參數(shù)的構(gòu)造方法,無參數(shù)的構(gòu)造方法默認的 value 初始值是 0  ,帶參數(shù)的構(gòu)造方法可以指定初始值。

AtomicInteger中的方法有哪些

AtomicInteger 中的方法

下面我們就來聊一下 AtomicInteger 中的方法。

Get 和 Set

我們首先來看一下最簡單的 get 、set 方法:

get() : 獲取當(dāng)前 AtomicInteger 的值

set() : 設(shè)置當(dāng)前 AtomicInteger 的值

get() 可以原子性的讀取 AtomicInteger 中的數(shù)據(jù),set() 可以原子性的設(shè)置當(dāng)前的值,因為 get() 和 set() 最終都是作用于  value 變量,而 value 是由 volatile 修飾的,所以 get 、set 相當(dāng)于都是對內(nèi)存進行讀取和設(shè)置。如下圖所示

AtomicInteger中的方法有哪些

我們上面提到了 i++ 和 i++ 的非原子性操作,我們說可以使用 AtomicInteger 中的方法進行替換。

Incremental 操作

AtomicInteger 中的 Incremental 相關(guān)方法可以滿足我們的需求

  • getAndIncrement() : 原子性的增加當(dāng)前的值,并把結(jié)果返回。相當(dāng)于 i++的操作。

AtomicInteger中的方法有哪些

為了驗證是不是線程安全的,我們用下面的例子進行測試

public class TAtomicTest implements Runnable{      AtomicInteger atomicInteger = new AtomicInteger();      @Override     public void run() {         for(int i = 0;i < 10000;i++){             System.out.println(atomicInteger.getAndIncrement());         }     }     public static void main(String[] args) {          TAtomicTest tAtomicTest = new TAtomicTest();          Thread t1 = new Thread(tAtomicTest);         Thread t2 = new Thread(tAtomicTest);         t1.start();         t2.start();     }  }

通過輸出結(jié)果你會發(fā)現(xiàn)它是一個線程安全的操作,你可以修改 i 的值,但是最后的結(jié)果仍然是 i - 1,因為先取值,然后再 + 1,它的示意圖如下。

AtomicInteger中的方法有哪些

  • incrementAndGet 與此相反,首先執(zhí)行 + 1 操作,然后返回自增后的結(jié)果,該操作方法能夠確保對 value 的原子性操作。如下圖所示

AtomicInteger中的方法有哪些

Decremental 操作

與此相對,x-- 或者 x = x - 1 這樣的自減操作也是原子性的。我們?nèi)匀豢梢允褂?AtomicInteger  中的方法來替換

  • getAndDecrement : 返回當(dāng)前類型的 int 值,然后對 value 的值進行自減運算。下面是測試代碼

class TAtomicTestDecrement implements Runnable{      AtomicInteger atomicInteger = new AtomicInteger(20000);      @Override     public void run() {         for(int i = 0;i < 10000 ;i++){             System.out.println(atomicInteger.getAndDecrement());         }     }      public static void main(String[] args) {          TAtomicTestDecrement tAtomicTest = new TAtomicTestDecrement();          Thread t1 = new Thread(tAtomicTest);         Thread t2 = new Thread(tAtomicTest);         t1.start();         t2.start();      }  }

下面是 getAndDecrement 的示意圖

AtomicInteger中的方法有哪些

  • decrementAndGet:同樣的,decrementAndGet 方法就是先執(zhí)行遞減操作,然后再獲取 value 的值,示意圖如下

AtomicInteger中的方法有哪些

LazySet方法

volatile 有內(nèi)存屏障你知道嗎?

內(nèi)存屏障是啥啊?

內(nèi)存屏障,也稱內(nèi)存柵欄,內(nèi)存柵障,屏障指令等, 是一類同步屏障指令,是 CPU  或編譯器在對內(nèi)存隨機訪問的操作中的一個同步點,使得此點之前的所有讀寫操作都執(zhí)行后才可以開始執(zhí)行此點之后的操作。也是一個讓CPU  處理單元中的內(nèi)存狀態(tài)對其它處理單元可見的一項技術(shù)。

CPU  使用了很多優(yōu)化,使用緩存、指令重排等,其最終的目的都是為了性能,也就是說,當(dāng)一個程序執(zhí)行時,只要最終的結(jié)果是一樣的,指令是否被重排并不重要。所以指令的執(zhí)行時序并不是順序執(zhí)行的,而是亂序執(zhí)行的,這就會帶來很多問題,這也促使著內(nèi)存屏障的出現(xiàn)。

語義上,內(nèi)存屏障之前的所有寫操作都要寫入內(nèi)存;內(nèi)存屏障之后的讀操作都可以獲得同步屏障之前的寫操作的結(jié)果。因此,對于敏感的程序塊,寫操作之后、讀操作之前可以插入內(nèi)存屏障。

內(nèi)存屏障的開銷非常輕量級,但是再小也是有開銷的,LazySet 的作用正是如此,它會以普通變量的形式來讀寫變量。

也可以說是:「懶得設(shè)置屏障了」

GetAndSet 方法

以原子方式設(shè)置為給定值并返回舊值。

它的源碼就是調(diào)用了一下 unsafe 中的 getAndSetInt 方法,如下所示

AtomicInteger中的方法有哪些

就是先進行循環(huán),然后調(diào)用 getIntVolatile 方法,這個方法我在 cpp 中沒有找到,找到的小伙伴們記得及時告訴讓我學(xué)習(xí)一下。

循環(huán)直到 compareAndSwapInt 返回 false,這就說明使用 CAS 并沒有更新為新的值,所以 var5 返回的就是最新的內(nèi)存值。

CAS 方法

我們一直常說的 CAS 其實就是 CompareAndSet 方法,這個方法顧名思義,就是 「比較并更新」  的意思,當(dāng)然這是字面理解,字面理解有點偏差,其實人家的意思是先比較,如果滿足那么再進行更新。

AtomicInteger中的方法有哪些

上面給出了 CAS Java 層面的源碼,JDK 官方給它的解釋就是 「如果當(dāng)前值等于 expect 的值,那么就以原子性的方式將當(dāng)前值設(shè)置為  update 給定值」,這個方法會返回一個 boolean 類型,如果是 true 就表示比較并更新成功,否則表示失敗。

CAS 同時也是一種無鎖并發(fā)機制,也稱為 Lock Free,所以你覺得 Lock Free 很高大上嗎?并沒有。

下面我們構(gòu)建一個加鎖解鎖的 CASLock

class CASLock {      AtomicInteger atomicInteger = new AtomicInteger();     Thread currentThread = null;      public void tryLock() throws Exception{          boolean isLock = atomicInteger.compareAndSet(0, 1);         if(!isLock){             throw new Exception("加鎖失敗");         }          currentThread = Thread.currentThread();         System.out.println(currentThread + " tryLock");      }      public void unlock() {          int lockValue = atomicInteger.get();         if(lockValue == 0){             return;         }         if(currentThread == Thread.currentThread()){             atomicInteger.compareAndSet(1,0);             System.out.println(currentThread + " unlock");         }     }      public static void main(String[] args) {          CASLock casLock = new CASLock();          for(int i = 0;i < 5;i++){              new Thread(() -> {                 try {                     casLock.tryLock();                     Thread.sleep(10000);                 } catch (Exception e) {                     e.printStackTrace();                 }finally {                     casLock.unlock();                 }             }).start();         }      } }

在上面的代碼中,我們構(gòu)建了一個 CASLock,在 tryLock 方法中,我們先使用 CAS  方法進行更新,如果更新不成功則拋出異常,并把當(dāng)前線程設(shè)置為加鎖線程。在 unLock 方法中,我們先判斷當(dāng)前值是否為 0 ,如果是 0  就是我們愿意看到的結(jié)果,直接返回。否則是 1,則表示當(dāng)前線程還在加鎖,我們再來判斷一下當(dāng)前線程是否是加鎖線程,如果是則執(zhí)行解鎖操作。

那么我們上面提到的 compareAndSet,它其實可以解析為如下操作

// 偽代碼  // 當(dāng)前值 int v = 0; int a = 0; int b = 1;  if(compare(0,0) == true){   set(0,1); } else{   // 繼續(xù)向下執(zhí)行 }

也可以拿生活場景中的買票舉例子,你去景區(qū)旅游肯定要持票才能進,如果你拿著是假票或者不符合景區(qū)的票肯定是能夠被識別出來的,如果你沒有拿票拿你也肯定進不去景區(qū)。

廢話少說,這就祭出來 compareAndSet 的示意圖

AtomicInteger中的方法有哪些

weakCompareAndSet: 媽的非常認真看了好幾遍,發(fā)現(xiàn) JDK1.8 的這個方法和 compareAndSet  方法完全一摸一樣啊,坑我。。。

但是真的是這樣么?并不是,JDK 源碼很博大精深,才不會設(shè)計一個重復(fù)的方法,你想想 JDK 團隊也不是會犯這種低級團隊,但是原因是什么呢?

《Java 高并發(fā)詳解》這本書給出了我們一個答案

AtomicInteger中的方法有哪些

AddAndGet

AddAndGet 和 getAndIncrement、getAndAdd、incrementAndGet 等等方法都是使用了 do  ... while + CAS 操作,其實也就相當(dāng)于是一個自旋鎖,如果 CAS 修改成功就會一直循環(huán),修改失敗才會返回。示意圖如下

AtomicInteger中的方法有哪些

深入 AtomicInteger

我們上面探討了 AtomicInteger 的具體使用,同時我們知道 AtomicInteger 是依靠 volatile 和 CAS  來保證原子性的,那么我們下面就來分析一下為什么 CAS 能夠保證原子性,它的底層是什么?AtomicInteger 與樂觀鎖又有什么關(guān)系呢?

AtomicInteger 的底層實現(xiàn)原理我們再來瞧瞧這個可愛的 compareAndSetL(CAS) 方法,為什么就這兩行代碼就保證原子性了?

AtomicInteger中的方法有哪些

我們可以看到,這個 CAS 方法相當(dāng)于是調(diào)用了 unsafe 中的 compareAndSwapInt 方法,我們進到 unsafe  方能發(fā)中看一下具體實現(xiàn)。

AtomicInteger中的方法有哪些

compareAndSwapInt 是 sun.misc 中的方法,這個方法是一個 native 方法,它的底層是 C/C++ 實現(xiàn)的,所以我們需要看  C/C++ 的源碼。

知道 C/C++ 的牛逼之處了么。使用 Java 就是玩應(yīng)用和架構(gòu)的,C/C++ 是玩服務(wù)器、底層的。

compareAndSwapInt 的源碼在 jdk8u-dev/hotspot/src/share/vm/prims/unsafe.app  路徑下,它的源碼實現(xiàn)是

AtomicInteger中的方法有哪些

也就是 Unsafe_CompareAndSwapInt 方法,我們找到這個方法

AtomicInteger中的方法有哪些

C/C++ 源碼我也看不懂,但是這不妨礙我們找到關(guān)鍵代碼 Atomic::cmpxchg ,cmpxchg 是 x86 CPU  架構(gòu)的匯編指令,它的主要作用就是比較并交換操作數(shù)。我們繼續(xù)往下跟找一下這個指令的定義。

我們會發(fā)現(xiàn)對應(yīng)不同的 os,其底層實現(xiàn)方式不一樣

AtomicInteger中的方法有哪些

我們找到 Windows 的實現(xiàn)方式如下

AtomicInteger中的方法有哪些

我們繼續(xù)向下找,它其實定義的是第 216 行的代碼,我們找進去

AtomicInteger中的方法有哪些

此時就需要匯編指令和寄存器相關(guān)的知識了。

上面的 os::is-MP() 是多處理操作系統(tǒng)的接口,下面是 __asm ,它是 C/C++ 的關(guān)鍵字,用于調(diào)用內(nèi)聯(lián)匯編程序。

__asm 中的代碼是匯編程序,大致來說就是把 dest、exchange_value 、compare_value 的值都放在寄存器中,下面的  LOCK_IF_MP 中代碼的大致意思就是

AtomicInteger中的方法有哪些

如果是多處理器的話就會執(zhí)行 lock,然后進行比較操作。其中的 cmp 表示比較,mp 表示的就是 MultiProcess,je 表示相等跳轉(zhuǎn),L0  表示的是標識位。

我們回到上面的匯編指令,我們可以看到,CAS 的底層就是 cmpxchg 指令。

樂觀鎖

你有沒有這個疑問,為什么 AtomicInteger 可以獲取當(dāng)前值,那為什么還會出現(xiàn) expectValue 和 value  不一致的情況呢?

因為 AtomicInteger 只是一個原子性的工具類,它不具有排他性,它不像是 synchronized 或者是 lock  一樣具有互斥和排他性,還記得 AtomicInteger 中有兩個方法 get 和 set 嗎?它們只是用 volatile 修飾了一下,而 volatile  不具有原子性,所以可能會存在 expectValue 和 value 的當(dāng)前值不一致的情況,因此可能會出現(xiàn)重復(fù)修改。

針對上面這種情況的解決辦法有兩種,一種是使用 synchronized 和 lock  等類似的加鎖機制,這種鎖具有獨占性,也就是說同一時刻只能有一個線程來進行修改,這種方式能夠保證原子性,但是相對開銷比較大,這種鎖也叫做悲觀鎖。另外一種解決辦法是使用版本號或者是  CAS 方法。

「版本號」

版本號機制是在數(shù)據(jù)表中加上一個 version 字段來實現(xiàn)的,表示數(shù)據(jù)被修改的次數(shù),當(dāng)執(zhí)行寫操作并且寫入成功后,version = version +  1,當(dāng)線程 A 要更新數(shù)據(jù)時,在讀取數(shù)據(jù)的同時也會讀取 version 值,在提交更新時,若剛才讀取到的 version 值為當(dāng)前數(shù)據(jù)庫中的 version  值相等時才更新,否則重試更新操作,直到更新成功。

「CAS 方法」

還有一種方式就是 CAS 了,我們上面用了大量的篇幅來介紹 CAS  方法,那么我們認為你現(xiàn)在已經(jīng)對其運行機制有一定的了解了,我們就不再闡述它的運行機制了。

任何事情都是有利也有弊,軟件行業(yè)沒有完美的解決方案只有最優(yōu)的解決方案,所以樂觀鎖也有它的弱點和缺陷,那就是 ABA 問題。

ABA 問題

ABA 問題說的是,如果一個變量第一次讀取的值是 A,準備好需要對 A 進行寫操作的時候,發(fā)現(xiàn)值還是 A,那么這種情況下,能認為 A  的值沒有被改變過嗎?可以是由 A -> B -> A 的這種情況,但是 AtomicInteger  卻不會這么認為,它只相信它看到的,它看到的是什么就是什么。舉個例子來說

假如現(xiàn)在有一個單鏈表,如下圖所示

AtomicInteger中的方法有哪些

A.next = B ,B.next = null,此時有兩個線程 T1 和 T2 分別從單鏈表中取出 A ,由于一些特殊原因,T2 把 A 改為 B  ,然后又改為 A ,此時 T1 執(zhí)行 CAS 方法,發(fā)現(xiàn)單鏈表仍然是 A ,就會執(zhí)行 CAS 方法,雖然結(jié)果沒錯,但是這種操作會造成一些潛在的問題。

AtomicInteger中的方法有哪些

此時還是一個單鏈表,兩個線程 T1 和 T2 分別從單鏈表中取出 A ,然后 T1 把鏈表改為 ACD 如下圖所示

AtomicInteger中的方法有哪些

此時 T2,發(fā)現(xiàn)內(nèi)存值還是 A ,就會把 A 的值嘗試替換為 B ,因為 B 的引用是 null,此時就會造成 C、D 處于游離態(tài)

AtomicInteger中的方法有哪些

JDK 1.5 以后的 AtomicStampedReference類就提供了此種能力,其中的 compareAndSet  方法就是首先檢查當(dāng)前值是否等于預(yù)期值,判斷的標準就是當(dāng)前引用和郵戳分別和預(yù)期引用和郵戳相等,如果全部相等,則以原子方式設(shè)置為給定的更新值。

AtomicInteger中的方法有哪些

好了,上面就是 Java 代碼流程了,看到 native 我們知道又要擼 cpp 了。開擼

AtomicInteger中的方法有哪些

簡單解釋一下就是 UnsafeWrapper 就是包裝器,換個名字而已。然后經(jīng)過一些 JNI 的處理,因為 compareAndSwapOject  比較的是引用,所以需要經(jīng)過 C++ 面向?qū)ο蟮霓D(zhuǎn)換。最主要的方法是 atomic_compare_exchange_oop

AtomicInteger中的方法有哪些

可以看到,又出現(xiàn)了熟悉的詞匯 cmpxchg ,也就是說 compareAndSwapOject 使用的還是 cmpxchg  原子性指令,只是它經(jīng)過了一系列轉(zhuǎn)換。

到此,關(guān)于“AtomicInteger中的方法有哪些”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>

分享名稱:AtomicInteger中的方法有哪些
瀏覽地址:http://bm7419.com/article10/pccigo.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供全網(wǎng)營銷推廣、虛擬主機、用戶體驗、外貿(mào)建站、商城網(wǎng)站網(wǎng)站制作

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

商城網(wǎng)站建設(shè)