這篇文章主要介紹“AtomicInteger中的方法有哪些”,在日常操作中,相信很多人在AtomicInteger中的方法有哪些問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”AtomicInteger中的方法有哪些”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!
10年的月湖網(wǎng)站建設(shè)經(jīng)驗(yàn),針對設(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è)計,從而大程度地提升瀏覽體驗(yàn)。創(chuàng)新互聯(lián)建站從事“月湖網(wǎng)站設(shè)計”,“月湖網(wǎng)站推廣”以來,每個客戶項目都認(rèn)真落實(shí)執(zhí)行。
了解 AtomicInteger
AtomicInteger 是 JDK1.5 新添加的工具類,我們首先來看一下它的繼承關(guān)系
與 int 的包裝類 Integer 一樣,都是繼承于 Number 類的。
這個 Number 類是基本數(shù)據(jù)類型的包裝類,一般和數(shù)據(jù)類型有關(guān)的對象都會繼承于 Number 類。
它的繼承體系很簡單,下面我們來看一下它的基本屬性和方法
AtomicInteger 的基本屬性
AtomicInteger 的基本屬性有三個
Unsafe 是 sun.misc 包下面的類,AtomicInteger 主要是依賴于 sun.misc.Unsafe 提供的一些 native 方法保證操作的原子性。
Unsafe 的 objectFieldOffset 方法可以獲取成員屬性在內(nèi)存中的地址相對于對象內(nèi)存地址的偏移量。說得簡單點(diǎn)就是找到這個變量在內(nèi)存中的地址,便于后續(xù)通過內(nèi)存地址直接進(jìn)行操作,這個值就是 value這個我們后面會再細(xì)說
value 就是 AtomicIneger 的值。
AtomicInteger 的構(gòu)造方法
繼續(xù)往下看,AtomicInteger 的構(gòu)造方法只有兩個,一個是無參數(shù)的構(gòu)造方法,無參數(shù)的構(gòu)造方法默認(rèn)的 value 初始值是 0 ,帶參數(shù)的構(gòu)造方法可以指定初始值。
AtomicInteger 中的方法
下面我們就來聊一下 AtomicInteger 中的方法。
Get 和 Set
我們首先來看一下最簡單的 get 、set 方法:
get() : 獲取當(dāng)前 AtomicInteger 的值
set() : 設(shè)置當(dāng)前 AtomicInteger 的值
get() 可以原子性的讀取 AtomicInteger 中的數(shù)據(jù),set() 可以原子性的設(shè)置當(dāng)前的值,因?yàn)?get() 和 set() 最終都是作用于 value 變量,而 value 是由 volatile 修飾的,所以 get 、set 相當(dāng)于都是對內(nèi)存進(jìn)行讀取和設(shè)置。如下圖所示
我們上面提到了 i++ 和 i++ 的非原子性操作,我們說可以使用 AtomicInteger 中的方法進(jìn)行替換。
Incremental 操作
AtomicInteger 中的 Incremental 相關(guān)方法可以滿足我們的需求
getAndIncrement() : 原子性的增加當(dāng)前的值,并把結(jié)果返回。相當(dāng)于 i++的操作。
為了驗(yàn)證是不是線程安全的,我們用下面的例子進(jìn)行測試
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,因?yàn)橄热≈担缓笤?+ 1,它的示意圖如下。
incrementAndGet 與此相反,首先執(zhí)行 + 1 操作,然后返回自增后的結(jié)果,該操作方法能夠確保對 value 的原子性操作。如下圖所示
Decremental 操作
與此相對,x-- 或者 x = x - 1 這樣的自減操作也是原子性的。我們?nèi)匀豢梢允褂?AtomicInteger 中的方法來替換
getAndDecrement : 返回當(dāng)前類型的 int 值,然后對 value 的值進(jìn)行自減運(yùn)算。下面是測試代碼
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 的示意圖
decrementAndGet:同樣的,decrementAndGet 方法就是先執(zhí)行遞減操作,然后再獲取 value 的值,示意圖如下
LazySet方法
volatile 有內(nèi)存屏障你知道嗎?
內(nèi)存屏障是啥啊?
內(nèi)存屏障,也稱內(nèi)存柵欄,內(nèi)存柵障,屏障指令等, 是一類同步屏障指令,是 CPU 或編譯器在對內(nèi)存隨機(jī)訪問的操作中的一個同步點(diǎn),使得此點(diǎn)之前的所有讀寫操作都執(zhí)行后才可以開始執(zhí)行此點(diǎn)之后的操作。也是一個讓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 方法,如下所示
就是先進(jìn)行循環(huán),然后調(diào)用 getIntVolatile 方法,這個方法我在 cpp 中沒有找到,找到的小伙伴們記得及時告訴讓我學(xué)習(xí)一下。
循環(huán)直到 compareAndSwapInt 返回 false,這就說明使用 CAS 并沒有更新為新的值,所以 var5 返回的就是最新的內(nèi)存值。
CAS 方法
我們一直常說的 CAS 其實(shí)就是 CompareAndSet 方法,這個方法顧名思義,就是 「比較并更新」 的意思,當(dāng)然這是字面理解,字面理解有點(diǎn)偏差,其實(shí)人家的意思是先比較,如果滿足那么再進(jìn)行更新。
上面給出了 CAS Java 層面的源碼,JDK 官方給它的解釋就是 「如果當(dāng)前值等于 expect 的值,那么就以原子性的方式將當(dāng)前值設(shè)置為 update 給定值」,這個方法會返回一個 boolean 類型,如果是 true 就表示比較并更新成功,否則表示失敗。
CAS 同時也是一種無鎖并發(fā)機(jī)制,也稱為 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 方法進(jìn)行更新,如果更新不成功則拋出異常,并把當(dāng)前線程設(shè)置為加鎖線程。在 unLock 方法中,我們先判斷當(dāng)前值是否為 0 ,如果是 0 就是我們愿意看到的結(jié)果,直接返回。否則是 1,則表示當(dāng)前線程還在加鎖,我們再來判斷一下當(dāng)前線程是否是加鎖線程,如果是則執(zhí)行解鎖操作。
那么我們上面提到的 compareAndSet,它其實(shí)可以解析為如下操作
// 偽代碼 // 當(dāng)前值 int v = 0; int a = 0; int b = 1; if(compare(0,0) == true){ set(0,1); } else{ // 繼續(xù)向下執(zhí)行 }
也可以拿生活場景中的買票舉例子,你去景區(qū)旅游肯定要持票才能進(jìn),如果你拿著是假票或者不符合景區(qū)的票肯定是能夠被識別出來的,如果你沒有拿票拿你也肯定進(jìn)不去景區(qū)。
廢話少說,這就祭出來 compareAndSet 的示意圖
weakCompareAndSet: 媽的非常認(rèn)真看了好幾遍,發(fā)現(xiàn) JDK1.8 的這個方法和 compareAndSet 方法完全一摸一樣啊,坑我。。。
但是真的是這樣么?并不是,JDK 源碼很博大精深,才不會設(shè)計一個重復(fù)的方法,你想想 JDK 團(tuán)隊也不是會犯這種低級團(tuán)隊,但是原因是什么呢?
《Java 高并發(fā)詳解》這本書給出了我們一個答案
AddAndGet
AddAndGet 和 getAndIncrement、getAndAdd、incrementAndGet 等等方法都是使用了 do ... while + CAS 操作,其實(shí)也就相當(dāng)于是一個自旋鎖,如果 CAS 修改成功就會一直循環(huán),修改失敗才會返回。示意圖如下
深入 AtomicInteger
我們上面探討了 AtomicInteger 的具體使用,同時我們知道 AtomicInteger 是依靠 volatile 和 CAS 來保證原子性的,那么我們下面就來分析一下為什么 CAS 能夠保證原子性,它的底層是什么?AtomicInteger 與樂觀鎖又有什么關(guān)系呢?
AtomicInteger 的底層實(shí)現(xiàn)原理我們再來瞧瞧這個可愛的 compareAndSetL(CAS) 方法,為什么就這兩行代碼就保證原子性了?
我們可以看到,這個 CAS 方法相當(dāng)于是調(diào)用了 unsafe 中的 compareAndSwapInt 方法,我們進(jìn)到 unsafe 方能發(fā)中看一下具體實(shí)現(xiàn)。
compareAndSwapInt 是 sun.misc 中的方法,這個方法是一個 native 方法,它的底層是 C/C++ 實(shí)現(xiàn)的,所以我們需要看 C/C++ 的源碼。
知道 C/C++ 的牛逼之處了么。使用 Java 就是玩應(yīng)用和架構(gòu)的,C/C++ 是玩服務(wù)器、底層的。
compareAndSwapInt 的源碼在 jdk8u-dev/hotspot/src/share/vm/prims/unsafe.app 路徑下,它的源碼實(shí)現(xiàn)是
也就是 Unsafe_CompareAndSwapInt 方法,我們找到這個方法
C/C++ 源碼我也看不懂,但是這不妨礙我們找到關(guān)鍵代碼 Atomic::cmpxchg ,cmpxchg 是 x86 CPU 架構(gòu)的匯編指令,它的主要作用就是比較并交換操作數(shù)。我們繼續(xù)往下跟找一下這個指令的定義。
我們會發(fā)現(xiàn)對應(yīng)不同的 os,其底層實(shí)現(xiàn)方式不一樣
我們找到 Windows 的實(shí)現(xiàn)方式如下
我們繼續(xù)向下找,它其實(shí)定義的是第 216 行的代碼,我們找進(jìn)去
此時就需要匯編指令和寄存器相關(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 中代碼的大致意思就是
如果是多處理器的話就會執(zhí)行 lock,然后進(jìn)行比較操作。其中的 cmp 表示比較,mp 表示的就是 MultiProcess,je 表示相等跳轉(zhuǎn),L0 表示的是標(biāo)識位。
我們回到上面的匯編指令,我們可以看到,CAS 的底層就是 cmpxchg 指令。
樂觀鎖
你有沒有這個疑問,為什么 AtomicInteger 可以獲取當(dāng)前值,那為什么還會出現(xiàn) expectValue 和 value 不一致的情況呢?
因?yàn)?AtomicInteger 只是一個原子性的工具類,它不具有排他性,它不像是 synchronized 或者是 lock 一樣具有互斥和排他性,還記得 AtomicInteger 中有兩個方法 get 和 set 嗎?它們只是用 volatile 修飾了一下,而 volatile 不具有原子性,所以可能會存在 expectValue 和 value 的當(dāng)前值不一致的情況,因此可能會出現(xiàn)重復(fù)修改。
針對上面這種情況的解決辦法有兩種,一種是使用 synchronized 和 lock 等類似的加鎖機(jī)制,這種鎖具有獨(dú)占性,也就是說同一時刻只能有一個線程來進(jìn)行修改,這種方式能夠保證原子性,但是相對開銷比較大,這種鎖也叫做悲觀鎖。另外一種解決辦法是使用版本號或者是 CAS 方法。
「版本號」
版本號機(jī)制是在數(shù)據(jù)表中加上一個 version 字段來實(shí)現(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 方法,那么我們認(rèn)為你現(xiàn)在已經(jīng)對其運(yùn)行機(jī)制有一定的了解了,我們就不再闡述它的運(yùn)行機(jī)制了。
任何事情都是有利也有弊,軟件行業(yè)沒有完美的解決方案只有最優(yōu)的解決方案,所以樂觀鎖也有它的弱點(diǎn)和缺陷,那就是 ABA 問題。
ABA 問題
ABA 問題說的是,如果一個變量第一次讀取的值是 A,準(zhǔn)備好需要對 A 進(jìn)行寫操作的時候,發(fā)現(xiàn)值還是 A,那么這種情況下,能認(rèn)為 A 的值沒有被改變過嗎?可以是由 A -> B -> A 的這種情況,但是 AtomicInteger 卻不會這么認(rèn)為,它只相信它看到的,它看到的是什么就是什么。舉個例子來說
假如現(xiàn)在有一個單鏈表,如下圖所示
A.next = B ,B.next = null,此時有兩個線程 T1 和 T2 分別從單鏈表中取出 A ,由于一些特殊原因,T2 把 A 改為 B ,然后又改為 A ,此時 T1 執(zhí)行 CAS 方法,發(fā)現(xiàn)單鏈表仍然是 A ,就會執(zhí)行 CAS 方法,雖然結(jié)果沒錯,但是這種操作會造成一些潛在的問題。
此時還是一個單鏈表,兩個線程 T1 和 T2 分別從單鏈表中取出 A ,然后 T1 把鏈表改為 ACD 如下圖所示
此時 T2,發(fā)現(xiàn)內(nèi)存值還是 A ,就會把 A 的值嘗試替換為 B ,因?yàn)?B 的引用是 null,此時就會造成 C、D 處于游離態(tài)
JDK 1.5 以后的 AtomicStampedReference類就提供了此種能力,其中的 compareAndSet 方法就是首先檢查當(dāng)前值是否等于預(yù)期值,判斷的標(biāo)準(zhǔn)就是當(dāng)前引用和郵戳分別和預(yù)期引用和郵戳相等,如果全部相等,則以原子方式設(shè)置為給定的更新值。
好了,上面就是 Java 代碼流程了,看到 native 我們知道又要擼 cpp 了。開擼
簡單解釋一下就是 UnsafeWrapper 就是包裝器,換個名字而已。然后經(jīng)過一些 JNI 的處理,因?yàn)?compareAndSwapOject 比較的是引用,所以需要經(jīng)過 C++ 面向?qū)ο蟮霓D(zhuǎn)換。最主要的方法是 atomic_compare_exchange_oop
可以看到,又出現(xiàn)了熟悉的詞匯 cmpxchg ,也就是說 compareAndSwapOject 使用的還是 cmpxchg 原子性指令,只是它經(jīng)過了一系列轉(zhuǎn)換。
到此,關(guān)于“AtomicInteger中的方法有哪些”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!