成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

告訴你一個(gè) AtomicInteger 的驚天大秘密!

開發(fā) 前端
在 JDK1.5 之前,為了確保在多線程下對某基本數(shù)據(jù)類型或者引用數(shù)據(jù)類型運(yùn)算的原子性,必須依賴于外部關(guān)鍵字 synchronized,但是這種情況在 JDK1.5 之后發(fā)生了改觀,當(dāng)然你依然可以使用 synchronized 來保證原子性,我們這里所說的一種線程安全的方式是原子性的工具類,比如 「AtomicInteger、AtomicBoolean」 等。

[[342909]]

 i++ 不是線程安全的操作,因?yàn)樗皇且粋€(gè)原子性操作。

那么,如果我想要達(dá)到類似 i++ 的這種效果,我應(yīng)該使用哪些集合或者說工具類呢?

在 JDK1.5 之前,為了確保在多線程下對某基本數(shù)據(jù)類型或者引用數(shù)據(jù)類型運(yùn)算的原子性,必須依賴于外部關(guān)鍵字 synchronized,但是這種情況在 JDK1.5 之后發(fā)生了改觀,當(dāng)然你依然可以使用 synchronized 來保證原子性,我們這里所說的一種線程安全的方式是原子性的工具類,比如 「AtomicInteger、AtomicBoolean」 等。這些原子類都是線程安全的工具類,他們同時(shí)也是 Lock-Free 的。下面我們就來一起認(rèn)識一下這些工具類以及 Lock - Free 是個(gè)什么概念。

了解 AtomicInteger

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

 

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

 

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

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

AtomicInteger 的基本屬性

AtomicInteger 的基本屬性有三個(gè)

 

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

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

value 就是 AtomicIneger 的值。

AtomicInteger 的構(gòu)造方法

繼續(xù)往下看,AtomicInteger 的構(gòu)造方法只有兩個(gè),一個(gè)是無參數(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)行測試

  1. public class TAtomicTest implements Runnable{ 
  2.  
  3.     AtomicInteger atomicInteger = new AtomicInteger(); 
  4.  
  5.     @Override 
  6.     public void run() { 
  7.         for(int i = 0;i < 10000;i++){ 
  8.             System.out.println(atomicInteger.getAndIncrement()); 
  9.         } 
  10.     } 
  11.     public static void main(String[] args) { 
  12.  
  13.         TAtomicTest tAtomicTest = new TAtomicTest(); 
  14.  
  15.         Thread t1 = new Thread(tAtomicTest); 
  16.         Thread t2 = new Thread(tAtomicTest); 
  17.         t1.start(); 
  18.         t2.start(); 
  19.     } 
  20.  

通過輸出結(jié)果你會(huì)發(fā)現(xiàn)它是一個(gè)線程安全的操作,你可以修改 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)算。下面是測試代碼
  1. class TAtomicTestDecrement implements Runnable{ 
  2.  
  3.     AtomicInteger atomicInteger = new AtomicInteger(20000); 
  4.  
  5.     @Override 
  6.     public void run() { 
  7.         for(int i = 0;i < 10000 ;i++){ 
  8.             System.out.println(atomicInteger.getAndDecrement()); 
  9.         } 
  10.     } 
  11.  
  12.     public static void main(String[] args) { 
  13.  
  14.         TAtomicTestDecrement tAtomicTest = new TAtomicTestDecrement(); 
  15.  
  16.         Thread t1 = new Thread(tAtomicTest); 
  17.         Thread t2 = new Thread(tAtomicTest); 
  18.         t1.start(); 
  19.         t2.start(); 
  20.  
  21.     } 
  22.  

下面是 getAndDecrement 的示意圖

 

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

 

LazySet方法

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

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

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

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

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

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

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

 

GetAndSet 方法

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

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

 

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

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

CAS 方法

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

 

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

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

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

  1. class CASLock { 
  2.  
  3.     AtomicInteger atomicInteger = new AtomicInteger(); 
  4.     Thread currentThread = null
  5.  
  6.     public void tryLock() throws Exception{ 
  7.  
  8.         boolean isLock = atomicInteger.compareAndSet(0, 1); 
  9.         if(!isLock){ 
  10.             throw new Exception("加鎖失敗"); 
  11.         } 
  12.  
  13.         currentThread = Thread.currentThread(); 
  14.         System.out.println(currentThread + " tryLock"); 
  15.  
  16.     } 
  17.  
  18.     public void unlock() { 
  19.  
  20.         int lockValue = atomicInteger.get(); 
  21.         if(lockValue == 0){ 
  22.             return
  23.         } 
  24.         if(currentThread == Thread.currentThread()){ 
  25.             atomicInteger.compareAndSet(1,0); 
  26.             System.out.println(currentThread + " unlock"); 
  27.         } 
  28.     } 
  29.  
  30.     public static void main(String[] args) { 
  31.  
  32.         CASLock casLock = new CASLock(); 
  33.  
  34.         for(int i = 0;i < 5;i++){ 
  35.  
  36.             new Thread(() -> { 
  37.                 try { 
  38.                     casLock.tryLock(); 
  39.                     Thread.sleep(10000); 
  40.                 } catch (Exception e) { 
  41.                     e.printStackTrace(); 
  42.                 }finally { 
  43.                     casLock.unlock(); 
  44.                 } 
  45.             }).start(); 
  46.         } 
  47.  
  48.     } 

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

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

  1. // 偽代碼 
  2.  
  3. // 當(dāng)前值 
  4. int v = 0; 
  5. int a = 0; 
  6. int b = 1; 
  7.  
  8. if(compare(0,0) == true){ 
  9.   set(0,1); 
  10. else
  11.   // 繼續(xù)向下執(zhí)行 

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

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

 

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

 

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

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

 

AddAndGet

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

 

深入 AtomicInteger

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

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

 

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

 

compareAndSwapInt 是 sun.misc 中的方法,這個(gè)方法是一個(gè) 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 方法,我們找到這個(gè)方法

 

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

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

 

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

 

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

 

此時(shí)就需要匯編指令和寄存器相關(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 中代碼的大致意思就是

 

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

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

樂觀鎖

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

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

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

「版本號」

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

「CAS 方法」

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

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

ABA 問題

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

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

 

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

 

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

 

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

 

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

 

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

 

簡單解釋一下就是 UnsafeWrapper 就是包裝器,換個(gè)名字而已。然后經(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)換。

后記

拋出來一個(gè)問題,CAS 能保證變量之間的可見性么?為什么?

還有一個(gè)問題,getIntVolatile 方法的 cpp 源碼在哪里?怎么找?

 本文轉(zhuǎn)載自微信公眾號「Java建設(shè)者」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系Java建設(shè)者公眾號。

 

責(zé)任編輯:武曉燕 來源: Java建設(shè)者
相關(guān)推薦

2020-04-17 14:16:10

SQL數(shù)據(jù)庫HTTP

2016-05-24 10:16:23

網(wǎng)絡(luò)竊案黑客銀行入侵事件

2021-08-18 11:24:51

幣圈黑客安全顧問

2023-11-17 22:56:47

ChatGPTAI

2021-08-12 05:29:53

區(qū)塊鏈加密貨幣黑客

2018-03-20 10:46:11

2011-07-07 09:47:33

2011-07-07 09:38:50

2016-10-19 09:00:57

漏洞郵箱秘密

2018-11-01 16:58:56

蘋果iPad ProMacBook Air

2015-09-21 14:22:43

2017-11-09 19:40:40

2015-11-02 10:32:43

bat騰訊百度

2018-08-07 09:56:56

2014-03-05 09:17:43

編程愛好

2010-05-13 00:03:44

2023-08-17 11:53:22

2011-06-30 09:37:08

JavaDB2SQL

2016-06-27 16:29:04

戴爾閃存

2018-07-05 11:17:04

華為云
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號

主站蜘蛛池模板: 日韩视频在线播放 | 亚洲精品黄色 | 男女羞羞视频在线 | 视频一区 亚洲 | 黄网站在线播放 | 久久久久久久综合色一本 | 综合第一页 | 欧美一级久久 | 欧美日韩亚洲一区 | 福利视频一区二区 | 日韩精品一区中文字幕 | 久久黄色 | 日韩精品一区在线 | 亚洲 精品 综合 精品 自拍 | 日韩精品久久久久 | 国产精品成人久久久久a级 久久蜜桃av一区二区天堂 | 亚洲一区二区三区在线播放 | 91精品国产综合久久婷婷香蕉 | 视频一区在线播放 | 久久成人免费视频 | 日韩www| 一区二区在线 | 在线精品一区二区 | 欧美另类视频在线 | 性高湖久久久久久久久3小时 | 亚洲网站在线播放 | 国产免费a | 在线免费91| 国产精品久久久久久久久久免费看 | 精品国产欧美 | 亚洲午夜三级 | 久久大陆| 青青草视频网站 | 日韩精品一区二区不卡 | 亚洲日本中文字幕在线 | 欧美无乱码久久久免费午夜一区 | 日本精品一区二区在线观看 | 性一交一乱一透一a级 | 亚洲欧美在线免费观看 | 成人乱人乱一区二区三区软件 | 国产精品毛片av一区 |