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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

Java并发编程—无锁互斥机制及CAS原理

發布時間:2024/4/15 java 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java并发编程—无锁互斥机制及CAS原理 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

目錄

一、CAS簡介

二、AtomicInteger代碼演示

三、CAS 實現

四、弊端


一、CAS簡介

在計算機科學中,比較和交換(Conmpare And Swap)是用于實現多線程同步的原子指令。它將內存位置的內容與給定值進行比較,只有在相同的情況下,將該內存位置的內容修改為新的給定值。這是作為單個原子操作完成的。 原子性保證新值基于最新信息計算; 如果該值在同一時間被另一個線程更新,則寫入將失敗。操作結果必須說明是否進行替換; 這可以通過一個簡單的布爾響應(這個變體通常稱為比較和設置),或通過返回從內存位置讀取的值來完成。

CAS是一種無鎖算法,有3個關鍵操作數?內存中的原數據V,舊的預期值A,需要修改的新值B,當內存值和舊的內存中預期值相等時,將內存中的值更新為新值。

操縱步驟:比較 A 與 V 是否相等。(比較) 如果比較相等,將 B 寫入 V。(交換) 返回操作是否成功。 當多個線程同時對某個資源進行CAS操作,只能有一個線程操作成功,但是并不會阻塞其他線程,其他線程只會收到操作失敗的信號。可見 CAS 其實是一個樂觀鎖

?

如上圖中,主存中保存V值,線程中要使用V值要先從主存中讀取V值到線程的工作內存A中,然后計算后變成B值,最后再把B值寫回到內存V值中。多個線程共用V值都是如此操作。CAS的核心是在將B值寫入到V之前要比較A值和V值是否相同,如果不相同證明此時V值已經被其他線程改變,重新將V值賦給A,并重新計算得到B,如果相同,則將B值賦給V。

如果不使用CAS機制,看看存在什么問題:假如V=1,現在Thread1要對V進行加1,Thread2也要對V進行加1,首先Thread1讀取V=1到自己工作內存A中此時A=1,假設Thread2此時也讀取V=1到自己的工作內存A中,分別進行加1操作后,兩個線程中B的值都為2,此時寫回到V中時發現V的值為2,但是兩個線程分別對V進行加處理結果卻只加了1有問題。

樂觀鎖與悲觀鎖:CAS屬于樂觀鎖,樂觀鎖就是每次不加鎖而是假設沒有沖突而去完成某項操作,如果因為沖突失敗就重試,直到成功為止。synchronized是悲觀鎖,被一個線程拿到鎖之后,其他線程必須等待該線程釋放鎖,性能較差

二、AtomicInteger代碼演示

在java中,a++不是原子操作,一個簡單的a++操作涉及到三個操作,獲取變量a的內存值,將變量a+1,將新值寫入內存,這里涉及到了兩次內存訪問,如果在多線程環境下,那么會出現并發安全問題。AtomicInteger是一個原子操作類,內部采用的就是CAS無鎖算法。 這里我們分析一下它的內部實現。

AtomicInteger atomicInteger = new AtomicInteger(0); atomicInteger.getAndSet(1);

???? 這里的靜態代碼塊AtomicInteger對象初始化之前就執行,獲取AtomicInteger對象value字段相對AtomicInteger對象的”起始地址”的偏移量,Java對象在內存中存儲的布局可以分為三塊區域:對象頭(Header)、實例數據(Instance Data)和對齊填充(Padding),”起始地址”的偏移量即是對象頭的偏移量。

static {try {valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));} catch (Exception ex) { throw new Error(ex); } } public final int getAndSet(int newValue) {return unsafe.getAndSetInt(this, valueOffset, newValue); }

每次通過內存地址(var2)先從內存中獲取內存中原值(var5),再循環將內存中的原值(var5)與給定內存地址(var2)相比較,如果相等則更新指定預期值(var4),如果不相等則再重試直到成功為止,最后返回舊的內存原值var5。

//var1為AtomicInteger對象,var2為內存地址值,var4為指定的預期值 public final int getAndSetInt(Object var1, long var2, int var4) {int var5;do {//unsafe.getIntVolatile調用本地方法獲取內存中值var5 = this.getIntVolatile(var1, var2);} while(!this.compareAndSwapInt(var1, var2, var5, var4));return var5; }

三、CAS 實現

?java.util.concurrent.atomic 包下的原子類 AtomicInteger 中的 compareAndSet 方法最終調用的是 sum.misc.Unsafe 這個類。 看名稱 Unsafe 就是一個不安全的類,這個類是利用了 Java 的類和包在可見性的的規則中的一個恰到好處處的漏洞。Unsafe 這個類為了速度,在Java的安全標準上做出了一定的妥協。再往下尋找我們發現 Unsafe的compareAndSwapInt 是 Native 的方法:

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

也就是說,這幾個 CAS 的方法應該是使用了本地的方法。所以這幾個方法的具體實現需要我們自己去 jdk 的源碼中搜索。 最終到搜索 cmpxchg 函數

inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) { // 判斷是否是多核 CPUint mp = os::is_MP();__asm { // 將參數值放入寄存器中mov edx, dest // 注意: dest 是指針類型,這里是把內存地址存入 edx 寄存器中mov ecx, exchange_valuemov eax, compare_value // LOCK_IF_MPcmp mp, 0/** 如果 mp = 0,表明是線程運行在單核 CPU 環境下。此時 je 會跳轉到 L0 標記處,* 也就是越過 _emit 0xF0 指令,直接執行 cmpxchg 指令。也就是不在下面的 cmpxchg 指令* 前加 lock 前綴。*/je L0 /** 0xF0 是 lock 前綴的機器碼,這里沒有使用 lock,而是直接使用了機器碼的形式。至于這樣做的* 原因可以參考知乎的一個回答:* https://www.zhihu.com/question/50878124/answer/123099923*/ _emit 0xF0L0: /** 比較并交換。簡單解釋一下下面這條指令,熟悉匯編的朋友可以略過下面的解釋:* cmpxchg: 即“比較并交換”指令* dword: 全稱是 double word,在 x86/x64 體系中,一個 * word = 2 byte,dword = 4 byte = 32 bit* ptr: 全稱是 pointer,與前面的 dword 連起來使用,表明訪問的內存單元是一個雙字單元* [edx]: [...] 表示一個內存單元,edx 是寄存器,dest 指針值存放在 edx 中。* 那么 [edx] 表示內存地址為 dest 的內存單元* * 這一條指令的意思就是,將 eax 寄存器中的值(compare_value)與 [edx] 雙字內存單元中的值* 進行對比,如果相同,則將 ecx 寄存器中的值(exchange_value)存入 [edx] 內存單元中。*/cmpxchg dword ptr [edx], ecx} }

總結一下 JAVA 的 cas 是怎么實現的:

  • java 的 cas 利用的的是 unsafe 這個類提供的 cas 操作。
  • unsafe 的cas 依賴了的是 jvm 針對不同的操作系統實現的 Atomic::cmpxchg
  • Atomic::cmpxchg 的實現使用了匯編的 cas 操作,并使用 cpu 硬件提供的 lock信號保證其原子性

四、弊端

1. ABA問題

???? CAS在操作的時候會檢查變量的值是否被更改過,如果沒有則更新值,但是帶來一個問題,最開始的值是A,接著變成B,最后又變成了A。經過檢查這個值確實沒有修改過,因為最后的值還是A,但是實際上這個值確實已經被修改過了。為了解決這個問題,在每次進行操作的時候加上一個版本號,每次操作的就是兩個值,一個版本號和某個值,A——>B——>A問題就變成了1A——>2B——>3A。在jdk中提供了AtomicStampedReference類解決ABA問題,用Pair這個內部類實現,包含兩個屬性,分別代表版本號和引用,在compareAndSet中先對當前引用進行檢查,再對版本號標志進行檢查,只有全部相等才更新值。

2. 只能保證一個共享變量的原子操作

???? 多個共享變量操作時,循環CAS就無法保證操作的原子性,這個時候就可以用鎖。從java1.5開始,JDK提供了AtomicReference類來保證引用對象之間的原子性,就可以把多個變量放在一個對象里來進行CAS操作。

3. 循環時間長CPU開銷較大

???? 在并發量比較高的情況下,如果許多線程反復嘗試更新某一個變量,卻又一直更新不成功,循環往復,會給CPU帶來很大的壓力。

總結

以上是生活随笔為你收集整理的Java并发编程—无锁互斥机制及CAS原理的全部內容,希望文章能夠幫你解決所遇到的問題。

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